mirror of https://github.com/hyperledger/besu
EIP-7220: Updates (Exit -> Withdrawal Request) (#6967)
Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com> Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net> Co-authored-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>pull/7029/head
parent
34acf8c839
commit
9cd50e1405
@ -1,85 +0,0 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.ValidatorExit; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class PragueValidatorExitsValidator implements ValidatorExitsValidator { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PragueValidatorExitsValidator.class); |
||||
|
||||
@Override |
||||
public boolean allowValidatorExits() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean validateValidatorExitParameter( |
||||
final Optional<List<ValidatorExit>> validatorExits) { |
||||
return validatorExits.isPresent(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean validateExitsInBlock(final Block block, final List<ValidatorExit> expectedExits) { |
||||
final Hash blockHash = block.getHash(); |
||||
|
||||
if (block.getHeader().getExitsRoot().isEmpty()) { |
||||
LOG.warn("Block {} must contain exits_root", blockHash); |
||||
return false; |
||||
} |
||||
|
||||
if (block.getBody().getExits().isEmpty()) { |
||||
LOG.warn("Block {} must contain exits (even if empty list)", blockHash); |
||||
return false; |
||||
} |
||||
|
||||
final List<ValidatorExit> exitsInBlock = block.getBody().getExits().get(); |
||||
// TODO Do we need to allow for customization? (e.g. if the value changes in the next fork)
|
||||
if (exitsInBlock.size() > ValidatorExitContractHelper.MAX_EXITS_PER_BLOCK) { |
||||
LOG.warn("Block {} has more than the allowed maximum number of exits", blockHash); |
||||
return false; |
||||
} |
||||
|
||||
// Validate exits_root
|
||||
final Hash expectedExitsRoot = BodyValidation.exitsRoot(exitsInBlock); |
||||
if (!expectedExitsRoot.equals(block.getHeader().getExitsRoot().get())) { |
||||
LOG.warn( |
||||
"Block {} exits_root does not match expected hash root for exits in block", blockHash); |
||||
return false; |
||||
} |
||||
|
||||
// Validate exits
|
||||
final boolean expectedExitsMatch = expectedExits.equals(exitsInBlock); |
||||
if (!expectedExitsMatch) { |
||||
LOG.warn( |
||||
"Block {} has a mismatch between its exits and expected exits (in_block = {}, expected = {})", |
||||
blockHash, |
||||
exitsInBlock, |
||||
expectedExits); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,94 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.WithdrawalRequest; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class PragueWithdrawalRequestValidator implements WithdrawalRequestValidator { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PragueWithdrawalRequestValidator.class); |
||||
|
||||
@Override |
||||
public boolean allowWithdrawalRequests() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean validateWithdrawalRequestParameter( |
||||
final Optional<List<WithdrawalRequest>> withdrawalRequests) { |
||||
return withdrawalRequests.isPresent(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean validateWithdrawalRequestsInBlock( |
||||
final Block block, final List<WithdrawalRequest> withdrawalRequests) { |
||||
final Hash blockHash = block.getHash(); |
||||
|
||||
if (block.getHeader().getWithdrawalRequestsRoot().isEmpty()) { |
||||
LOG.warn("Block {} must contain withdrawal_requests_root", blockHash); |
||||
return false; |
||||
} |
||||
|
||||
if (block.getBody().getWithdrawalRequests().isEmpty()) { |
||||
LOG.warn("Block {} must contain withdrawal requests (even if empty list)", blockHash); |
||||
return false; |
||||
} |
||||
|
||||
final List<WithdrawalRequest> withdrawalRequestsInBlock = |
||||
block.getBody().getWithdrawalRequests().get(); |
||||
// TODO Do we need to allow for customization? (e.g. if the value changes in the next fork)
|
||||
if (withdrawalRequestsInBlock.size() |
||||
> WithdrawalRequestContractHelper.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) { |
||||
LOG.warn( |
||||
"Block {} has more than the allowed maximum number of withdrawal requests", blockHash); |
||||
return false; |
||||
} |
||||
|
||||
// Validate exits_root
|
||||
final Hash expectedWithdrawalsRequestRoot = |
||||
BodyValidation.withdrawalRequestsRoot(withdrawalRequestsInBlock); |
||||
if (!expectedWithdrawalsRequestRoot.equals( |
||||
block.getHeader().getWithdrawalRequestsRoot().get())) { |
||||
LOG.warn( |
||||
"Block {} withdrawal_requests_root does not match expected hash root for withdrawal requests in block", |
||||
blockHash); |
||||
return false; |
||||
} |
||||
|
||||
// Validate exits
|
||||
final boolean expectedWithdrawalRequestMatch = |
||||
withdrawalRequests.equals(withdrawalRequestsInBlock); |
||||
if (!expectedWithdrawalRequestMatch) { |
||||
LOG.warn( |
||||
"Block {} has a mismatch between its withdrawal requests and expected withdrawal requests (in_block = {}, " |
||||
+ "expected = {})", |
||||
blockHash, |
||||
withdrawalRequestsInBlock, |
||||
withdrawalRequests); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
@ -1,146 +0,0 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.BLSPublicKey; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.ethereum.core.ValidatorExit; |
||||
import org.hyperledger.besu.evm.account.Account; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.units.bigints.UInt256; |
||||
|
||||
/** |
||||
* Helper for interacting with the Validator Exit Contract (https://eips.ethereum.org/EIPS/eip-7002)
|
||||
* |
||||
* <p>TODO: Please note that this is not the spec-way of interacting with the Validator Exit |
||||
* contract. See https://github.com/hyperledger/besu/issues/6918 for more information.
|
||||
*/ |
||||
public class ValidatorExitContractHelper { |
||||
|
||||
public static final Address VALIDATOR_EXIT_ADDRESS = |
||||
Address.fromHexString("0x0f1ee3e66777F27a7703400644C6fCE41527E017"); |
||||
|
||||
@VisibleForTesting |
||||
// Storage slot to store the difference between number of exits since last block and target exits
|
||||
// per block
|
||||
static final UInt256 EXCESS_EXITS_STORAGE_SLOT = UInt256.valueOf(0L); |
||||
|
||||
@VisibleForTesting |
||||
// Storage slot to store the number of exits added since last block
|
||||
static final UInt256 EXIT_COUNT_STORAGE_SLOT = UInt256.valueOf(1L); |
||||
|
||||
@VisibleForTesting |
||||
static final UInt256 EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT = UInt256.valueOf(2L); |
||||
|
||||
@VisibleForTesting |
||||
static final UInt256 EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT = UInt256.valueOf(3L); |
||||
|
||||
private static final UInt256 EXIT_MESSAGE_QUEUE_STORAGE_OFFSET = UInt256.valueOf(4L); |
||||
// How many slots each exit occupies in the account state
|
||||
private static final int EXIT_MESSAGE_STORAGE_SLOT_SIZE = 3; |
||||
@VisibleForTesting static final int MAX_EXITS_PER_BLOCK = 16; |
||||
private static final int TARGET_EXITS_PER_BLOCK = 2; |
||||
|
||||
/* |
||||
Pop the expected list of exits from the validator exit smart contract, updating the queue pointers and other |
||||
control variables in the contract state. |
||||
*/ |
||||
public static List<ValidatorExit> popExitsFromQueue(final MutableWorldState mutableWorldState) { |
||||
final WorldUpdater worldUpdater = mutableWorldState.updater(); |
||||
final MutableAccount account = worldUpdater.getAccount(VALIDATOR_EXIT_ADDRESS); |
||||
if (Hash.EMPTY.equals(account.getCodeHash())) { |
||||
return List.of(); |
||||
} |
||||
|
||||
final List<ValidatorExit> exits = dequeueExits(account); |
||||
updateExcessExits(account); |
||||
resetExitCount(account); |
||||
|
||||
worldUpdater.commit(); |
||||
|
||||
return exits; |
||||
} |
||||
|
||||
private static List<ValidatorExit> dequeueExits(final MutableAccount account) { |
||||
final UInt256 queueHeadIndex = account.getStorageValue(EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT); |
||||
final UInt256 queueTailIndex = account.getStorageValue(EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT); |
||||
|
||||
final List<ValidatorExit> exits = peekExpectedExits(account, queueHeadIndex, queueTailIndex); |
||||
|
||||
final UInt256 newQueueHeadIndex = queueHeadIndex.plus(exits.size()); |
||||
if (newQueueHeadIndex.equals(queueTailIndex)) { |
||||
// Queue is empty, reset queue pointers
|
||||
account.setStorageValue(EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT, UInt256.valueOf(0L)); |
||||
account.setStorageValue(EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT, UInt256.valueOf(0L)); |
||||
} else { |
||||
account.setStorageValue(EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT, newQueueHeadIndex); |
||||
} |
||||
|
||||
return exits; |
||||
} |
||||
|
||||
private static List<ValidatorExit> peekExpectedExits( |
||||
final Account account, final UInt256 queueHeadIndex, final UInt256 queueTailIndex) { |
||||
final long numExitsInQueue = queueTailIndex.subtract(queueHeadIndex).toLong(); |
||||
final long numExitsDequeued = Long.min(numExitsInQueue, MAX_EXITS_PER_BLOCK); |
||||
|
||||
final List<ValidatorExit> exits = new ArrayList<>(); |
||||
|
||||
for (int i = 0; i < numExitsDequeued; i++) { |
||||
final UInt256 queueStorageSlot = |
||||
EXIT_MESSAGE_QUEUE_STORAGE_OFFSET.plus( |
||||
queueHeadIndex.plus(i).multiply(EXIT_MESSAGE_STORAGE_SLOT_SIZE)); |
||||
final Address sourceAddress = |
||||
Address.wrap(account.getStorageValue(queueStorageSlot).toBytes().slice(12, 20)); |
||||
final BLSPublicKey validatorPubKey = |
||||
BLSPublicKey.wrap( |
||||
Bytes.concatenate( |
||||
account |
||||
.getStorageValue(queueStorageSlot.plus(1)) |
||||
.toBytes() |
||||
.slice(0, 32), // no need to slice
|
||||
account.getStorageValue(queueStorageSlot.plus(2)).toBytes().slice(0, 16))); |
||||
|
||||
exits.add(new ValidatorExit(sourceAddress, validatorPubKey)); |
||||
} |
||||
|
||||
return exits; |
||||
} |
||||
|
||||
private static void updateExcessExits(final MutableAccount account) { |
||||
final UInt256 previousExcessExits = account.getStorageValue(EXCESS_EXITS_STORAGE_SLOT); |
||||
final UInt256 exitCount = account.getStorageValue(EXIT_COUNT_STORAGE_SLOT); |
||||
|
||||
UInt256 newExcessExits = UInt256.valueOf(0L); |
||||
if (previousExcessExits.plus(exitCount).toLong() > TARGET_EXITS_PER_BLOCK) { |
||||
newExcessExits = previousExcessExits.plus(exitCount).subtract(TARGET_EXITS_PER_BLOCK); |
||||
} |
||||
|
||||
account.setStorageValue(EXCESS_EXITS_STORAGE_SLOT, newExcessExits); |
||||
} |
||||
|
||||
private static void resetExitCount(final MutableAccount account) { |
||||
account.setStorageValue(EXIT_COUNT_STORAGE_SLOT, UInt256.valueOf(0L)); |
||||
} |
||||
} |
@ -1,76 +0,0 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.ValidatorExit; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public interface ValidatorExitsValidator { |
||||
|
||||
boolean allowValidatorExits(); |
||||
|
||||
boolean validateValidatorExitParameter(Optional<List<ValidatorExit>> validatorExits); |
||||
|
||||
boolean validateExitsInBlock(Block block, List<ValidatorExit> validatorExits); |
||||
|
||||
/** Used before Prague */ |
||||
class ProhibitedExits implements ValidatorExitsValidator { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProhibitedExits.class); |
||||
|
||||
@Override |
||||
public boolean allowValidatorExits() { |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Before Prague we do not expect to have execution layer triggered exits, so it is expected the |
||||
* optional parameter will be empty |
||||
* |
||||
* @param validatorExits Optional list of exits |
||||
* @return true, if valid, false otherwise |
||||
*/ |
||||
@Override |
||||
public boolean validateValidatorExitParameter( |
||||
final Optional<List<ValidatorExit>> validatorExits) { |
||||
return validatorExits.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean validateExitsInBlock( |
||||
final Block block, final List<ValidatorExit> validatorExits) { |
||||
final Optional<List<ValidatorExit>> maybeExits = block.getBody().getExits(); |
||||
if (maybeExits.isPresent()) { |
||||
LOG.warn("Block {} contains exits but exits are prohibited", block.getHash()); |
||||
return false; |
||||
} |
||||
|
||||
if (block.getHeader().getExitsRoot().isPresent()) { |
||||
LOG.warn("Block {} header contains exits_root but exits are prohibited", block.getHash()); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,186 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.BLSPublicKey; |
||||
import org.hyperledger.besu.datatypes.GWei; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.ethereum.core.WithdrawalRequest; |
||||
import org.hyperledger.besu.evm.account.Account; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.units.bigints.UInt256; |
||||
import org.apache.tuweni.units.bigints.UInt64; |
||||
|
||||
/** |
||||
* Helper for interacting with the Validator Withdrawal Request Contract |
||||
* (https://eips.ethereum.org/EIPS/eip-7002)
|
||||
* |
||||
* <p>TODO: Please note that this is not the spec-way of interacting with the Validator Withdrawal |
||||
* Request contract. See https://github.com/hyperledger/besu/issues/6918 for more information.
|
||||
*/ |
||||
public class WithdrawalRequestContractHelper { |
||||
|
||||
public static final Address WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = |
||||
Address.fromHexString("0xEd8EA01d70Cb49726175BCf2778B9C982912e017"); |
||||
|
||||
@VisibleForTesting |
||||
// Storage slot to store the difference between number of withdrawal requests since last block and
|
||||
// target withdrawal requests
|
||||
// per block
|
||||
static final UInt256 EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT = UInt256.valueOf(0L); |
||||
|
||||
@VisibleForTesting |
||||
// Storage slot to store the number of withdrawal requests added since last block
|
||||
static final UInt256 WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT = UInt256.valueOf(1L); |
||||
|
||||
@VisibleForTesting |
||||
static final UInt256 WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT = UInt256.valueOf(2L); |
||||
|
||||
@VisibleForTesting |
||||
static final UInt256 WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT = UInt256.valueOf(3L); |
||||
|
||||
private static final UInt256 WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET = UInt256.valueOf(4L); |
||||
|
||||
// How many slots each withdrawal request occupies in the account state
|
||||
private static final int WITHDRAWAL_REQUEST_STORAGE_SLOT_SIZE = 3; |
||||
|
||||
@VisibleForTesting static final int MAX_WITHDRAWAL_REQUESTS_PER_BLOCK = 16; |
||||
|
||||
private static final int TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK = 2; |
||||
|
||||
// TODO-lucas Add MIN_WITHDRAWAL_REQUEST_FEE and WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION
|
||||
|
||||
/* |
||||
Pop the expected list of withdrawal requests from the smart contract, updating the queue pointers and other |
||||
control variables in the contract state. |
||||
*/ |
||||
public static List<WithdrawalRequest> popWithdrawalRequestsFromQueue( |
||||
final MutableWorldState mutableWorldState) { |
||||
final WorldUpdater worldUpdater = mutableWorldState.updater(); |
||||
final MutableAccount account = worldUpdater.getAccount(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS); |
||||
if (Hash.EMPTY.equals(account.getCodeHash())) { |
||||
return List.of(); |
||||
} |
||||
|
||||
final List<WithdrawalRequest> withdrawalRequests = dequeueWithdrawalRequests(account); |
||||
updateExcessWithdrawalRequests(account); |
||||
resetWithdrawalRequestsCount(account); |
||||
|
||||
worldUpdater.commit(); |
||||
|
||||
return withdrawalRequests; |
||||
} |
||||
|
||||
private static List<WithdrawalRequest> dequeueWithdrawalRequests(final MutableAccount account) { |
||||
final UInt256 queueHeadIndex = |
||||
account.getStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT); |
||||
final UInt256 queueTailIndex = |
||||
account.getStorageValue(WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT); |
||||
|
||||
final List<WithdrawalRequest> withdrawalRequests = |
||||
peekExpectedWithdrawalRequests(account, queueHeadIndex, queueTailIndex); |
||||
|
||||
final UInt256 newQueueHeadIndex = queueHeadIndex.plus(withdrawalRequests.size()); |
||||
if (newQueueHeadIndex.equals(queueTailIndex)) { |
||||
// Queue is empty, reset queue pointers
|
||||
account.setStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, UInt256.valueOf(0L)); |
||||
account.setStorageValue(WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, UInt256.valueOf(0L)); |
||||
} else { |
||||
account.setStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, newQueueHeadIndex); |
||||
} |
||||
|
||||
return withdrawalRequests; |
||||
} |
||||
|
||||
/* |
||||
;; Each stack element has the following layout: |
||||
;; |
||||
;; A: addr |
||||
;; 0x00 | 00 00 00 00 00 00 00 00 00 00 00 00 aa aa aa aa |
||||
;; 0x10 | aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa |
||||
;; |
||||
;; B: pk[0:32] |
||||
;; 0x00 | bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb |
||||
;; 0x10 | bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb |
||||
;; |
||||
;; C: pk[32:48] ++ am[0:8] -> pk2_am |
||||
;; 0x00 | cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc |
||||
;; 0x10 | dd dd dd dd dd dd dd dd 00 00 00 00 00 00 00 00 |
||||
;; |
||||
;; To get these three stack elements into the correct contiguous format, it is |
||||
;; necessary to combine them in the follow form: |
||||
;; |
||||
;; (A[12:32] ++ B[0:12], B[12:32] ++ C[0:12], C[12:24]) |
||||
*/ |
||||
private static List<WithdrawalRequest> peekExpectedWithdrawalRequests( |
||||
final Account account, final UInt256 queueHeadIndex, final UInt256 queueTailIndex) { |
||||
final long numRequestsInQueue = queueTailIndex.subtract(queueHeadIndex).toLong(); |
||||
final long numRequestsDequeued = |
||||
Long.min(numRequestsInQueue, MAX_WITHDRAWAL_REQUESTS_PER_BLOCK); |
||||
|
||||
final List<WithdrawalRequest> withdrawalRequests = new ArrayList<>(); |
||||
|
||||
for (int i = 0; i < numRequestsDequeued; i++) { |
||||
final UInt256 queueStorageSlot = |
||||
WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET.plus( |
||||
queueHeadIndex.plus(i).multiply(WITHDRAWAL_REQUEST_STORAGE_SLOT_SIZE)); |
||||
final Address sourceAddress = |
||||
Address.wrap(account.getStorageValue(queueStorageSlot).toBytes().slice(12, 20)); |
||||
final BLSPublicKey validatorPubKey = |
||||
BLSPublicKey.wrap( |
||||
Bytes.concatenate( |
||||
account |
||||
.getStorageValue(queueStorageSlot.plus(1)) |
||||
.toBytes() |
||||
.slice(0, 32), // no need to slice
|
||||
account.getStorageValue(queueStorageSlot.plus(2)).toBytes().slice(0, 16))); |
||||
final UInt64 amount = |
||||
UInt64.fromBytes(account.getStorageValue(queueStorageSlot.plus(2)).slice(16, 8)); |
||||
|
||||
withdrawalRequests.add( |
||||
new WithdrawalRequest(sourceAddress, validatorPubKey, GWei.of(amount))); |
||||
} |
||||
|
||||
return withdrawalRequests; |
||||
} |
||||
|
||||
private static void updateExcessWithdrawalRequests(final MutableAccount account) { |
||||
final UInt256 previousExcessRequests = |
||||
account.getStorageValue(EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT); |
||||
final UInt256 requestsCount = account.getStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT); |
||||
|
||||
UInt256 newExcessRequests = UInt256.valueOf(0L); |
||||
if (previousExcessRequests.plus(requestsCount).toLong() |
||||
> TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK) { |
||||
newExcessRequests = |
||||
previousExcessRequests.plus(requestsCount).subtract(TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK); |
||||
} |
||||
|
||||
account.setStorageValue(EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT, newExcessRequests); |
||||
} |
||||
|
||||
private static void resetWithdrawalRequestsCount(final MutableAccount account) { |
||||
account.setStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, UInt256.valueOf(0L)); |
||||
} |
||||
} |
@ -0,0 +1,82 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.WithdrawalRequest; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public interface WithdrawalRequestValidator { |
||||
|
||||
boolean allowWithdrawalRequests(); |
||||
|
||||
boolean validateWithdrawalRequestParameter(Optional<List<WithdrawalRequest>> withdrawalRequests); |
||||
|
||||
boolean validateWithdrawalRequestsInBlock( |
||||
Block block, List<WithdrawalRequest> withdrawalRequests); |
||||
|
||||
/** Used before Prague */ |
||||
class ProhibitedWithdrawalRequests implements WithdrawalRequestValidator { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProhibitedWithdrawalRequests.class); |
||||
|
||||
@Override |
||||
public boolean allowWithdrawalRequests() { |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Before Prague we do not expect to have execution layer withdrawal requests, so it is expected |
||||
* the optional parameter will be empty |
||||
* |
||||
* @param withdrawalRequests Optional list of withdrawal requests |
||||
* @return true, if valid, false otherwise |
||||
*/ |
||||
@Override |
||||
public boolean validateWithdrawalRequestParameter( |
||||
final Optional<List<WithdrawalRequest>> withdrawalRequests) { |
||||
return withdrawalRequests.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean validateWithdrawalRequestsInBlock( |
||||
final Block block, final List<WithdrawalRequest> withdrawalRequests) { |
||||
final Optional<List<WithdrawalRequest>> maybeWithdrawalRequests = |
||||
block.getBody().getWithdrawalRequests(); |
||||
if (maybeWithdrawalRequests.isPresent()) { |
||||
LOG.warn( |
||||
"Block {} contains withdrawal requests but withdrawal requests are prohibited", |
||||
block.getHash()); |
||||
return false; |
||||
} |
||||
|
||||
if (block.getHeader().getWithdrawalRequestsRoot().isPresent()) { |
||||
LOG.warn( |
||||
"Block {} header contains withdrawal_requests_root but withdrawal requests are prohibited", |
||||
block.getHash()); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
} |
@ -1,81 +0,0 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithExitsAndExitsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithExitsMismatch; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithExitsRootMismatch; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithExitsWithoutExitsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithMoreThanMaximumExits; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithoutExitsAndExitsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithoutExitsWithExitsRoot; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.ValidatorExit; |
||||
import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.ValidateExitTestParameter; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.Arguments; |
||||
import org.junit.jupiter.params.provider.MethodSource; |
||||
|
||||
class PragueValidatorExitsValidatorTest { |
||||
|
||||
@ParameterizedTest(name = "{index}: {0}") |
||||
@MethodSource("paramsForValidateValidatorExitParameter") |
||||
public void validateValidatorExitParameter( |
||||
final String description, |
||||
final Optional<List<ValidatorExit>> maybeExits, |
||||
final boolean expectedValidity) { |
||||
assertThat(new PragueValidatorExitsValidator().validateValidatorExitParameter(maybeExits)) |
||||
.isEqualTo(expectedValidity); |
||||
} |
||||
|
||||
private static Stream<Arguments> paramsForValidateValidatorExitParameter() { |
||||
return Stream.of( |
||||
Arguments.of("Allowed exits - validating empty exits", Optional.empty(), false), |
||||
Arguments.of("Allowed exits - validating present exits", Optional.of(List.of()), true)); |
||||
} |
||||
|
||||
@ParameterizedTest(name = "{index}: {0}") |
||||
@MethodSource("validateExitsInBlockParamsForPrague") |
||||
public void validateExitsInBlock_WhenPrague( |
||||
final ValidateExitTestParameter param, final boolean expectedValidity) { |
||||
assertThat( |
||||
new PragueValidatorExitsValidator() |
||||
.validateExitsInBlock(param.block, param.expectedExits)) |
||||
.isEqualTo(expectedValidity); |
||||
} |
||||
|
||||
private static Stream<Arguments> validateExitsInBlockParamsForPrague() { |
||||
return Stream.of( |
||||
Arguments.of(blockWithExitsAndExitsRoot(), true), |
||||
Arguments.of(blockWithExitsWithoutExitsRoot(), false), |
||||
Arguments.of(blockWithoutExitsWithExitsRoot(), false), |
||||
Arguments.of(blockWithoutExitsAndExitsRoot(), false), |
||||
Arguments.of(blockWithExitsRootMismatch(), false), |
||||
Arguments.of(blockWithExitsMismatch(), false), |
||||
Arguments.of(blockWithMoreThanMaximumExits(), false)); |
||||
} |
||||
|
||||
@Test |
||||
public void allowExitsShouldReturnTrue() { |
||||
assertThat(new PragueValidatorExitsValidator().allowValidatorExits()).isTrue(); |
||||
} |
||||
} |
@ -0,0 +1,82 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithMoreThanMaximumWithdrawalRequests; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsAndWithdrawalRequestsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsMismatch; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsRootMismatch; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsWithoutWithdrawalRequestsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithoutWithdrawalRequestsAndWithdrawalRequestsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithoutWithdrawalRequestsWithWithdrawalRequestsRoot; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.WithdrawalRequest; |
||||
import org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.WithdrawalRequestTestParameter; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.Arguments; |
||||
import org.junit.jupiter.params.provider.MethodSource; |
||||
|
||||
class PragueWithdrawalRequestValidatorTest { |
||||
|
||||
@ParameterizedTest(name = "{index}: {0}") |
||||
@MethodSource("paramsForValidateWithdrawalRequestParameter") |
||||
public void validateWithdrawalRequestParameter( |
||||
final String description, |
||||
final Optional<List<WithdrawalRequest>> maybeExits, |
||||
final boolean expectedValidity) { |
||||
assertThat( |
||||
new PragueWithdrawalRequestValidator().validateWithdrawalRequestParameter(maybeExits)) |
||||
.isEqualTo(expectedValidity); |
||||
} |
||||
|
||||
private static Stream<Arguments> paramsForValidateWithdrawalRequestParameter() { |
||||
return Stream.of( |
||||
Arguments.of("Allowed exits - validating empty exits", Optional.empty(), false), |
||||
Arguments.of("Allowed exits - validating present exits", Optional.of(List.of()), true)); |
||||
} |
||||
|
||||
@ParameterizedTest(name = "{index}: {0}") |
||||
@MethodSource("validateWithdrawalRequestsInBlockParamsForPrague") |
||||
public void validateWithdrawalRequestsInBlock_WhenPrague( |
||||
final WithdrawalRequestTestParameter param, final boolean expectedValidity) { |
||||
assertThat( |
||||
new PragueWithdrawalRequestValidator() |
||||
.validateWithdrawalRequestsInBlock(param.block, param.expectedWithdrawalRequest)) |
||||
.isEqualTo(expectedValidity); |
||||
} |
||||
|
||||
private static Stream<Arguments> validateWithdrawalRequestsInBlockParamsForPrague() { |
||||
return Stream.of( |
||||
Arguments.of(blockWithWithdrawalRequestsAndWithdrawalRequestsRoot(), true), |
||||
Arguments.of(blockWithWithdrawalRequestsWithoutWithdrawalRequestsRoot(), false), |
||||
Arguments.of(blockWithoutWithdrawalRequestsWithWithdrawalRequestsRoot(), false), |
||||
Arguments.of(blockWithoutWithdrawalRequestsAndWithdrawalRequestsRoot(), false), |
||||
Arguments.of(blockWithWithdrawalRequestsRootMismatch(), false), |
||||
Arguments.of(blockWithWithdrawalRequestsMismatch(), false), |
||||
Arguments.of(blockWithMoreThanMaximumWithdrawalRequests(), false)); |
||||
} |
||||
|
||||
@Test |
||||
public void allowExitsShouldReturnTrue() { |
||||
assertThat(new PragueWithdrawalRequestValidator().allowWithdrawalRequests()).isTrue(); |
||||
} |
||||
} |
@ -1,77 +0,0 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithExitsAndExitsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithExitsWithoutExitsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithoutExitsAndExitsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.blockWithoutExitsWithExitsRoot; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.ValidatorExit; |
||||
import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidatorTestFixtures.ValidateExitTestParameter; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.Arguments; |
||||
import org.junit.jupiter.params.provider.MethodSource; |
||||
|
||||
class ValidatorExitsValidatorTest { |
||||
|
||||
@ParameterizedTest(name = "{index}: {0}") |
||||
@MethodSource("paramsForValidateValidatorExitParameter") |
||||
public void validateValidatorExitParameter( |
||||
final String description, |
||||
final Optional<List<ValidatorExit>> maybeExits, |
||||
final boolean expectedValidity) { |
||||
assertThat( |
||||
new ValidatorExitsValidator.ProhibitedExits() |
||||
.validateValidatorExitParameter(maybeExits)) |
||||
.isEqualTo(expectedValidity); |
||||
} |
||||
|
||||
private static Stream<Arguments> paramsForValidateValidatorExitParameter() { |
||||
return Stream.of( |
||||
Arguments.of("Prohibited exits - validating empty exits", Optional.empty(), true), |
||||
Arguments.of("Prohibited exits - validating present exits", Optional.of(List.of()), false)); |
||||
} |
||||
|
||||
@ParameterizedTest(name = "{index}: {0}") |
||||
@MethodSource("validateExitsInBlockParamsForProhibited") |
||||
public void validateExitsInBlock_WhenProhibited( |
||||
final ValidateExitTestParameter param, final boolean expectedValidity) { |
||||
assertThat( |
||||
new ValidatorExitsValidator.ProhibitedExits() |
||||
.validateExitsInBlock(param.block, param.expectedExits)) |
||||
.isEqualTo(expectedValidity); |
||||
} |
||||
|
||||
private static Stream<Arguments> validateExitsInBlockParamsForProhibited() { |
||||
return Stream.of( |
||||
Arguments.of(blockWithExitsAndExitsRoot(), false), |
||||
Arguments.of(blockWithExitsWithoutExitsRoot(), false), |
||||
Arguments.of(blockWithoutExitsWithExitsRoot(), false), |
||||
Arguments.of(blockWithoutExitsAndExitsRoot(), true)); |
||||
} |
||||
|
||||
@Test |
||||
public void allowExitsShouldReturnFalse() { |
||||
assertThat(new ValidatorExitsValidator.ProhibitedExits().allowValidatorExits()).isFalse(); |
||||
} |
||||
} |
@ -1,155 +0,0 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitContractHelper.MAX_EXITS_PER_BLOCK; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.BLSPublicKey; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.BlockDataGenerator; |
||||
import org.hyperledger.besu.ethereum.core.ValidatorExit; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.stream.IntStream; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.apache.tuweni.bytes.Bytes48; |
||||
|
||||
public class ValidatorExitsValidatorTestFixtures { |
||||
|
||||
private static final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); |
||||
|
||||
static ValidateExitTestParameter blockWithExitsAndExitsRoot() { |
||||
final Optional<List<ValidatorExit>> maybeExits = Optional.of(List.of(createExit())); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setExitsRoot(BodyValidation.exitsRoot(maybeExits.get())) |
||||
.setExits(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new ValidateExitTestParameter("Block with exits and exits_root", block, maybeExits); |
||||
} |
||||
|
||||
static ValidateExitTestParameter blockWithoutExitsWithExitsRoot() { |
||||
final Optional<List<ValidatorExit>> maybeExits = Optional.empty(); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create().setExitsRoot(Hash.EMPTY).setExits(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new ValidateExitTestParameter( |
||||
"Block with exits_root but without exits", block, maybeExits); |
||||
} |
||||
|
||||
static ValidateExitTestParameter blockWithExitsWithoutExitsRoot() { |
||||
final Optional<List<ValidatorExit>> maybeExits = Optional.of(List.of(createExit())); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create().setExits(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new ValidateExitTestParameter( |
||||
"Block with exits but without exits_root", block, maybeExits); |
||||
} |
||||
|
||||
static ValidateExitTestParameter blockWithoutExitsAndExitsRoot() { |
||||
final Optional<List<ValidatorExit>> maybeExits = Optional.empty(); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create().setExits(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new ValidateExitTestParameter("Block without exits and exits_root", block, maybeExits); |
||||
} |
||||
|
||||
static ValidateExitTestParameter blockWithExitsRootMismatch() { |
||||
final Optional<List<ValidatorExit>> maybeExits = Optional.of(List.of(createExit())); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create().setExitsRoot(Hash.EMPTY).setExits(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new ValidateExitTestParameter("Block with exits_root mismatch", block, maybeExits); |
||||
} |
||||
|
||||
static ValidateExitTestParameter blockWithExitsMismatch() { |
||||
final Optional<List<ValidatorExit>> maybeExits = |
||||
Optional.of(List.of(createExit(), createExit())); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setExitsRoot(BodyValidation.exitsRoot(maybeExits.get())) |
||||
.setExits(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new ValidateExitTestParameter( |
||||
"Block with exits mismatch", block, maybeExits, List.of(createExit())); |
||||
} |
||||
|
||||
static ValidateExitTestParameter blockWithMoreThanMaximumExits() { |
||||
final List<ValidatorExit> validatorExits = |
||||
IntStream.range(0, MAX_EXITS_PER_BLOCK + 1).mapToObj(__ -> createExit()).toList(); |
||||
final Optional<List<ValidatorExit>> maybeExits = Optional.of(validatorExits); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setExitsRoot(BodyValidation.exitsRoot(maybeExits.get())) |
||||
.setExits(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new ValidateExitTestParameter("Block with more than maximum exits", block, maybeExits); |
||||
} |
||||
|
||||
static ValidatorExit createExit() { |
||||
return new ValidatorExit( |
||||
Address.extract(Bytes32.random()), BLSPublicKey.wrap(Bytes48.random())); |
||||
} |
||||
|
||||
static class ValidateExitTestParameter { |
||||
|
||||
String description; |
||||
Block block; |
||||
Optional<List<ValidatorExit>> maybeExits; |
||||
List<ValidatorExit> expectedExits; |
||||
|
||||
public ValidateExitTestParameter( |
||||
final String description, |
||||
final Block block, |
||||
final Optional<List<ValidatorExit>> maybeExits) { |
||||
this(description, block, maybeExits, maybeExits.orElseGet(List::of)); |
||||
} |
||||
|
||||
public ValidateExitTestParameter( |
||||
final String description, |
||||
final Block block, |
||||
final Optional<List<ValidatorExit>> maybeExits, |
||||
final List<ValidatorExit> expectedExits) { |
||||
this.description = description; |
||||
this.block = block; |
||||
this.maybeExits = maybeExits; |
||||
this.expectedExits = expectedExits; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return description; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsAndWithdrawalRequestsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsWithoutWithdrawalRequestsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithoutWithdrawalRequestsAndWithdrawalRequestsRoot; |
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithoutWithdrawalRequestsWithWithdrawalRequestsRoot; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.WithdrawalRequest; |
||||
import org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidator.ProhibitedWithdrawalRequests; |
||||
import org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.WithdrawalRequestTestParameter; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.Arguments; |
||||
import org.junit.jupiter.params.provider.MethodSource; |
||||
|
||||
class WithdrawalRequestValidatorTest { |
||||
|
||||
@ParameterizedTest(name = "{index}: {0}") |
||||
@MethodSource("paramsForValidateWithdrawalRequestParameter") |
||||
public void validateWithdrawalRequestParameter( |
||||
final String description, |
||||
final Optional<List<WithdrawalRequest>> maybeExits, |
||||
final boolean expectedValidity) { |
||||
assertThat(new ProhibitedWithdrawalRequests().validateWithdrawalRequestParameter(maybeExits)) |
||||
.isEqualTo(expectedValidity); |
||||
} |
||||
|
||||
private static Stream<Arguments> paramsForValidateWithdrawalRequestParameter() { |
||||
return Stream.of( |
||||
Arguments.of("Prohibited exits - validating empty exits", Optional.empty(), true), |
||||
Arguments.of("Prohibited exits - validating present exits", Optional.of(List.of()), false)); |
||||
} |
||||
|
||||
@ParameterizedTest(name = "{index}: {0}") |
||||
@MethodSource("validateWithdrawalRequestsInBlockParamsForProhibited") |
||||
public void validateWithdrawalRequestsInBlock_WhenProhibited( |
||||
final WithdrawalRequestTestParameter param, final boolean expectedValidity) { |
||||
assertThat( |
||||
new ProhibitedWithdrawalRequests() |
||||
.validateWithdrawalRequestsInBlock(param.block, param.expectedWithdrawalRequest)) |
||||
.isEqualTo(expectedValidity); |
||||
} |
||||
|
||||
private static Stream<Arguments> validateWithdrawalRequestsInBlockParamsForProhibited() { |
||||
return Stream.of( |
||||
Arguments.of(blockWithWithdrawalRequestsAndWithdrawalRequestsRoot(), false), |
||||
Arguments.of(blockWithWithdrawalRequestsWithoutWithdrawalRequestsRoot(), false), |
||||
Arguments.of(blockWithoutWithdrawalRequestsWithWithdrawalRequestsRoot(), false), |
||||
Arguments.of(blockWithoutWithdrawalRequestsAndWithdrawalRequestsRoot(), true)); |
||||
} |
||||
|
||||
@Test |
||||
public void allowExitsShouldReturnFalse() { |
||||
assertThat(new ProhibitedWithdrawalRequests().allowWithdrawalRequests()).isFalse(); |
||||
} |
||||
} |
@ -0,0 +1,175 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.BLSPublicKey; |
||||
import org.hyperledger.besu.datatypes.GWei; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.BlockDataGenerator; |
||||
import org.hyperledger.besu.ethereum.core.WithdrawalRequest; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.stream.IntStream; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.apache.tuweni.bytes.Bytes48; |
||||
|
||||
public class WithdrawalRequestValidatorTestFixtures { |
||||
|
||||
private static final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); |
||||
|
||||
static WithdrawalRequestTestParameter blockWithWithdrawalRequestsAndWithdrawalRequestsRoot() { |
||||
final Optional<List<WithdrawalRequest>> maybeWithdrawalRequests = |
||||
Optional.of(List.of(createWithdrawalRequest())); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setWithdrawalRequestsRoot( |
||||
BodyValidation.withdrawalRequestsRoot(maybeWithdrawalRequests.get())) |
||||
.setWithdrawalRequests(maybeWithdrawalRequests); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new WithdrawalRequestTestParameter( |
||||
"Block with withdrawal requests and withdrawal_requests_root", |
||||
block, |
||||
maybeWithdrawalRequests); |
||||
} |
||||
|
||||
static WithdrawalRequestTestParameter blockWithoutWithdrawalRequestsWithWithdrawalRequestsRoot() { |
||||
final Optional<List<WithdrawalRequest>> maybeExits = Optional.empty(); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setWithdrawalRequestsRoot(Hash.EMPTY) |
||||
.setWithdrawalRequests(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new WithdrawalRequestTestParameter( |
||||
"Block with withdrawal_requests_root but without withdrawal requests", block, maybeExits); |
||||
} |
||||
|
||||
static WithdrawalRequestTestParameter blockWithWithdrawalRequestsWithoutWithdrawalRequestsRoot() { |
||||
final Optional<List<WithdrawalRequest>> maybeExits = |
||||
Optional.of(List.of(createWithdrawalRequest())); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create().setWithdrawalRequests(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new WithdrawalRequestTestParameter( |
||||
"Block with withdrawal requests but without withdrawal_requests_root", block, maybeExits); |
||||
} |
||||
|
||||
static WithdrawalRequestTestParameter blockWithoutWithdrawalRequestsAndWithdrawalRequestsRoot() { |
||||
final Optional<List<WithdrawalRequest>> maybeExits = Optional.empty(); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create().setWithdrawalRequests(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new WithdrawalRequestTestParameter( |
||||
"Block without withdrawal requests and withdrawal_requests_root", block, maybeExits); |
||||
} |
||||
|
||||
static WithdrawalRequestTestParameter blockWithWithdrawalRequestsRootMismatch() { |
||||
final Optional<List<WithdrawalRequest>> maybeExits = |
||||
Optional.of(List.of(createWithdrawalRequest())); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setWithdrawalRequestsRoot(Hash.EMPTY) |
||||
.setWithdrawalRequests(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new WithdrawalRequestTestParameter( |
||||
"Block with withdrawal_requests_root mismatch", block, maybeExits); |
||||
} |
||||
|
||||
static WithdrawalRequestTestParameter blockWithWithdrawalRequestsMismatch() { |
||||
final Optional<List<WithdrawalRequest>> maybeExits = |
||||
Optional.of(List.of(createWithdrawalRequest(), createWithdrawalRequest())); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setWithdrawalRequestsRoot(BodyValidation.withdrawalRequestsRoot(maybeExits.get())) |
||||
.setWithdrawalRequests(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new WithdrawalRequestTestParameter( |
||||
"Block with withdrawal requests mismatch", |
||||
block, |
||||
maybeExits, |
||||
List.of(createWithdrawalRequest())); |
||||
} |
||||
|
||||
static WithdrawalRequestTestParameter blockWithMoreThanMaximumWithdrawalRequests() { |
||||
final List<WithdrawalRequest> validatorExits = |
||||
IntStream.range(0, MAX_WITHDRAWAL_REQUESTS_PER_BLOCK + 1) |
||||
.mapToObj(__ -> createWithdrawalRequest()) |
||||
.toList(); |
||||
final Optional<List<WithdrawalRequest>> maybeExits = Optional.of(validatorExits); |
||||
|
||||
final BlockDataGenerator.BlockOptions blockOptions = |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setWithdrawalRequestsRoot(BodyValidation.withdrawalRequestsRoot(maybeExits.get())) |
||||
.setWithdrawalRequests(maybeExits); |
||||
final Block block = blockDataGenerator.block(blockOptions); |
||||
|
||||
return new WithdrawalRequestTestParameter( |
||||
"Block with more than maximum withdrawal requests", block, maybeExits); |
||||
} |
||||
|
||||
static WithdrawalRequest createWithdrawalRequest() { |
||||
return new WithdrawalRequest( |
||||
Address.extract(Bytes32.random()), BLSPublicKey.wrap(Bytes48.random()), GWei.ONE); |
||||
} |
||||
|
||||
static class WithdrawalRequestTestParameter { |
||||
|
||||
String description; |
||||
Block block; |
||||
Optional<List<WithdrawalRequest>> maybeWithdrawalRequest; |
||||
List<WithdrawalRequest> expectedWithdrawalRequest; |
||||
|
||||
public WithdrawalRequestTestParameter( |
||||
final String description, |
||||
final Block block, |
||||
final Optional<List<WithdrawalRequest>> maybeWithdrawalRequest) { |
||||
this(description, block, maybeWithdrawalRequest, maybeWithdrawalRequest.orElseGet(List::of)); |
||||
} |
||||
|
||||
public WithdrawalRequestTestParameter( |
||||
final String description, |
||||
final Block block, |
||||
final Optional<List<WithdrawalRequest>> maybeWithdrawalRequest, |
||||
final List<WithdrawalRequest> expectedWithdrawalRequest) { |
||||
this.description = description; |
||||
this.block = block; |
||||
this.maybeWithdrawalRequest = maybeWithdrawalRequest; |
||||
this.expectedWithdrawalRequest = expectedWithdrawalRequest; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return description; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue