mirror of https://github.com/hyperledger/besu
EIP-7002: Validator Exit contract helper and adding exits to created blocks (#6883)
Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com>pull/6964/head
parent
f68db3801b
commit
61432831d5
File diff suppressed because one or more lines are too long
@ -0,0 +1,42 @@ |
|||||||
|
{ |
||||||
|
"request": { |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": "engine_newPayloadV3", |
||||||
|
"params": [ |
||||||
|
{ |
||||||
|
"parentHash": "0x2b3ae3a4c482f3dab43f0606af50dc8fd3ab981ba0659d477fa96955927736ae", |
||||||
|
"feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", |
||||||
|
"stateRoot": "0xd5d6e8c8d57e328871c5b81f078ab69e02466ab0e487c2c597effb4ffc185384", |
||||||
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"gasLimit": "0x1c9c380", |
||||||
|
"gasUsed": "0x0", |
||||||
|
"timestamp": "0x30", |
||||||
|
"extraData": "0x", |
||||||
|
"baseFeePerGas": "0x7", |
||||||
|
"transactions": [], |
||||||
|
"withdrawals": [], |
||||||
|
"depositReceipts": [], |
||||||
|
"exits": [], |
||||||
|
"blockNumber": "0x3", |
||||||
|
"blockHash": "0x0bd5e56ac3552719a1af061ec3f48248e817fc8ac7306d611d195ae023e9f771", |
||||||
|
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
"excessBlobGas": "0x0", |
||||||
|
"blobGasUsed": "0x0" |
||||||
|
}, |
||||||
|
[], |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000" |
||||||
|
], |
||||||
|
"id": 67 |
||||||
|
}, |
||||||
|
"response": { |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"id": 67, |
||||||
|
"result": { |
||||||
|
"status": "VALID", |
||||||
|
"latestValidHash": "0x0bd5e56ac3552719a1af061ec3f48248e817fc8ac7306d611d195ae023e9f771", |
||||||
|
"validationError": null |
||||||
|
} |
||||||
|
}, |
||||||
|
"statusCode": 200 |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"request": { |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": "eth_sendRawTransaction", |
||||||
|
"params": ["0xf8978085e8d4a51000832dc6c0940f1ee3e66777f27a7703400644c6fce41527e01702b08706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243822fdfa01527e82d4155c70f3dc6c1df4ba26f9fb9d7cea03a402a17d630dd5465a82a9aa0378b1a45916be48d98b8ef547df0daf34f2e85037360887d954ccacdc069b222"], |
||||||
|
"id": 67 |
||||||
|
}, |
||||||
|
"response": { |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"id": 67, |
||||||
|
"result": "0xf4aaedb9020f067d720daf555a4ccb6756741365defb4cd9c94c5ba39d64a5e5" |
||||||
|
}, |
||||||
|
"statusCode": 200 |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
{ |
||||||
|
"request": { |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": "engine_forkchoiceUpdatedV3", |
||||||
|
"params": [ |
||||||
|
{ |
||||||
|
"headBlockHash": "0x0bd5e56ac3552719a1af061ec3f48248e817fc8ac7306d611d195ae023e9f771", |
||||||
|
"safeBlockHash": "0x0bd5e56ac3552719a1af061ec3f48248e817fc8ac7306d611d195ae023e9f771", |
||||||
|
"finalizedBlockHash": "0x0bd5e56ac3552719a1af061ec3f48248e817fc8ac7306d611d195ae023e9f771" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"timestamp": "0x40", |
||||||
|
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"suggestedFeeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", |
||||||
|
"withdrawals": [], |
||||||
|
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||||
|
} |
||||||
|
], |
||||||
|
"id": 67 |
||||||
|
}, |
||||||
|
"response": { |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"id": 67, |
||||||
|
"result": { |
||||||
|
"payloadStatus": { |
||||||
|
"status": "VALID", |
||||||
|
"latestValidHash": "0x0bd5e56ac3552719a1af061ec3f48248e817fc8ac7306d611d195ae023e9f771", |
||||||
|
"validationError": null |
||||||
|
}, |
||||||
|
"payloadId": "0x282643bbede61941" |
||||||
|
} |
||||||
|
}, |
||||||
|
"statusCode" : 200 |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
{ |
||||||
|
"request": { |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": "engine_getPayloadV4", |
||||||
|
"params": [ |
||||||
|
"0x282643bbede61941" |
||||||
|
], |
||||||
|
"id": 67 |
||||||
|
}, |
||||||
|
"response": { |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"id": 67, |
||||||
|
"result": { |
||||||
|
"executionPayload": { |
||||||
|
"parentHash": "0x0bd5e56ac3552719a1af061ec3f48248e817fc8ac7306d611d195ae023e9f771", |
||||||
|
"feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", |
||||||
|
"stateRoot": "0x99b256355fb804ab33458099469f9a2904b4b4e9171d023334b84d3f0e3a8d43", |
||||||
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"gasLimit": "0x1c9c380", |
||||||
|
"gasUsed": "0x145b3", |
||||||
|
"timestamp": "0x40", |
||||||
|
"extraData": "0x", |
||||||
|
"baseFeePerGas": "0x7", |
||||||
|
"excessBlobGas": "0x0", |
||||||
|
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"transactions": [ |
||||||
|
"0xf8978085e8d4a51000832dc6c0940f1ee3e66777f27a7703400644c6fce41527e01702b08706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243822fdfa01527e82d4155c70f3dc6c1df4ba26f9fb9d7cea03a402a17d630dd5465a82a9aa0378b1a45916be48d98b8ef547df0daf34f2e85037360887d954ccacdc069b222" |
||||||
|
], |
||||||
|
"withdrawals": [], |
||||||
|
"depositReceipts": [], |
||||||
|
"exits": [ |
||||||
|
{ |
||||||
|
"sourceAddress": "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f", |
||||||
|
"validatorPubKey": "0x8706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243" |
||||||
|
} |
||||||
|
], |
||||||
|
"receiptsRoot": "0xf2e2f11f0c553ed811be4460880996149ab3947bd0d2c1330457925a11254514", |
||||||
|
"blobGasUsed": "0x0", |
||||||
|
"blockHash": "0xb26d2fa98315d4d4cdcae8e5590964787b3343c11ff64eb548179687a612d467", |
||||||
|
"blockNumber": "0x4" |
||||||
|
}, |
||||||
|
"blockValue": "0x12838c23cb1481b", |
||||||
|
"blobsBundle": { |
||||||
|
"commitments": [], |
||||||
|
"proofs": [], |
||||||
|
"blobs": [] |
||||||
|
}, |
||||||
|
"shouldOverrideBuilder": false |
||||||
|
} |
||||||
|
}, |
||||||
|
"statusCode": 200, |
||||||
|
"waitTime": 1500 |
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
/* |
||||||
|
* 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,146 @@ |
|||||||
|
/* |
||||||
|
* 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)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
/* |
||||||
|
* 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,197 @@ |
|||||||
|
/* |
||||||
|
* 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.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; |
||||||
|
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitContractHelper.EXCESS_EXITS_STORAGE_SLOT; |
||||||
|
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitContractHelper.EXIT_COUNT_STORAGE_SLOT; |
||||||
|
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitContractHelper.EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT; |
||||||
|
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitContractHelper.EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT; |
||||||
|
import static org.hyperledger.besu.ethereum.mainnet.ValidatorExitContractHelper.VALIDATOR_EXIT_ADDRESS; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.Address; |
||||||
|
import org.hyperledger.besu.datatypes.BLSPublicKey; |
||||||
|
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||||
|
import org.hyperledger.besu.ethereum.core.ValidatorExit; |
||||||
|
import org.hyperledger.besu.evm.account.MutableAccount; |
||||||
|
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
import java.util.stream.IntStream; |
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.apache.tuweni.bytes.Bytes32; |
||||||
|
import org.apache.tuweni.bytes.Bytes48; |
||||||
|
import org.apache.tuweni.units.bigints.UInt256; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
class ValidatorExitContractHelperTest { |
||||||
|
|
||||||
|
private MutableWorldState worldState; |
||||||
|
private MutableAccount contract; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
public void setUp() { |
||||||
|
worldState = createInMemoryWorldStateArchive().getMutable(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void popExitsFromQueue_ReadExitsCorrectly() { |
||||||
|
final List<ValidatorExit> validatorExits = List.of(createExit(), createExit(), createExit()); |
||||||
|
loadContractStorage(worldState, validatorExits); |
||||||
|
|
||||||
|
final List<ValidatorExit> poppedExits = |
||||||
|
ValidatorExitContractHelper.popExitsFromQueue(worldState); |
||||||
|
|
||||||
|
assertThat(poppedExits).isEqualTo(validatorExits); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void popExitsFromQueue_whenContractCodeIsEmpty_ReturnsEmptyListOfExits() { |
||||||
|
// Create account with empty code
|
||||||
|
final WorldUpdater updater = worldState.updater(); |
||||||
|
updater.createAccount(VALIDATOR_EXIT_ADDRESS); |
||||||
|
updater.commit(); |
||||||
|
|
||||||
|
assertThat(ValidatorExitContractHelper.popExitsFromQueue(worldState)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void popExitsFromQueue_WhenMoreExits_UpdatesQueuePointers() { |
||||||
|
// Loading contract with more than 16 exits
|
||||||
|
final List<ValidatorExit> validatorExits = |
||||||
|
IntStream.range(0, 30).mapToObj(__ -> createExit()).collect(Collectors.toList()); |
||||||
|
loadContractStorage(worldState, validatorExits); |
||||||
|
// After loading the contract, the exit count since last block should match the size of the list
|
||||||
|
assertContractStorageValue(EXIT_COUNT_STORAGE_SLOT, validatorExits.size()); |
||||||
|
|
||||||
|
final List<ValidatorExit> poppedExits = |
||||||
|
ValidatorExitContractHelper.popExitsFromQueue(worldState); |
||||||
|
assertThat(poppedExits).hasSize(16); |
||||||
|
|
||||||
|
// Check that queue pointers were updated successfully (head advanced to index 16)
|
||||||
|
assertContractStorageValue(EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT, 16); |
||||||
|
assertContractStorageValue(EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT, 30); |
||||||
|
|
||||||
|
// We had 30 exits in the queue, and target per block is 2, so we have 28 excess
|
||||||
|
assertContractStorageValue(EXCESS_EXITS_STORAGE_SLOT, 28); |
||||||
|
|
||||||
|
// We always reset the exit count after processing the queue
|
||||||
|
assertContractStorageValue(EXIT_COUNT_STORAGE_SLOT, 0); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void popExitsFromQueue_WhenNoMoreExits_ZeroQueuePointers() { |
||||||
|
final List<ValidatorExit> validatorExits = List.of(createExit(), createExit(), createExit()); |
||||||
|
loadContractStorage(worldState, validatorExits); |
||||||
|
// After loading the contract, the exit count since last block should match the size of the list
|
||||||
|
assertContractStorageValue(EXIT_COUNT_STORAGE_SLOT, validatorExits.size()); |
||||||
|
|
||||||
|
final List<ValidatorExit> poppedExits = |
||||||
|
ValidatorExitContractHelper.popExitsFromQueue(worldState); |
||||||
|
assertThat(poppedExits).hasSize(3); |
||||||
|
|
||||||
|
// Check that queue pointers were updated successfully (head and tail zero because queue is
|
||||||
|
// empty)
|
||||||
|
assertContractStorageValue(EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT, 0); |
||||||
|
assertContractStorageValue(EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT, 0); |
||||||
|
|
||||||
|
// We had 3 exits in the queue, target per block is 2, so we have 1 excess
|
||||||
|
assertContractStorageValue(EXCESS_EXITS_STORAGE_SLOT, 1); |
||||||
|
|
||||||
|
// We always reset the exit count after processing the queue
|
||||||
|
assertContractStorageValue(EXIT_COUNT_STORAGE_SLOT, 0); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void popExitsFromQueue_WhenNoExits_DoesNothing() { |
||||||
|
// Loading contract with 0 exits
|
||||||
|
loadContractStorage(worldState, List.of()); |
||||||
|
// After loading storage, we have the exit count as zero because no exits were aded
|
||||||
|
assertContractStorageValue(EXIT_COUNT_STORAGE_SLOT, 0); |
||||||
|
|
||||||
|
final List<ValidatorExit> poppedExits = |
||||||
|
ValidatorExitContractHelper.popExitsFromQueue(worldState); |
||||||
|
assertThat(poppedExits).hasSize(0); |
||||||
|
|
||||||
|
// Check that queue pointers are correct (head and tail are zero)
|
||||||
|
assertContractStorageValue(EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT, 0); |
||||||
|
assertContractStorageValue(EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT, 0); |
||||||
|
|
||||||
|
// We had 0 exits in the queue, and target per block is 2, so we have 0 excess
|
||||||
|
assertContractStorageValue(EXCESS_EXITS_STORAGE_SLOT, 0); |
||||||
|
|
||||||
|
// We always reset the exit count after processing the queue
|
||||||
|
assertContractStorageValue(EXIT_COUNT_STORAGE_SLOT, 0); |
||||||
|
} |
||||||
|
|
||||||
|
private void assertContractStorageValue(final UInt256 slot, final int expectedValue) { |
||||||
|
assertContractStorageValue(slot, UInt256.valueOf(expectedValue)); |
||||||
|
} |
||||||
|
|
||||||
|
private void assertContractStorageValue(final UInt256 slot, final UInt256 expectedValue) { |
||||||
|
assertThat(worldState.get(VALIDATOR_EXIT_ADDRESS).getStorageValue(slot)) |
||||||
|
.isEqualTo(expectedValue); |
||||||
|
} |
||||||
|
|
||||||
|
private void loadContractStorage( |
||||||
|
final MutableWorldState worldState, final List<ValidatorExit> exits) { |
||||||
|
final WorldUpdater updater = worldState.updater(); |
||||||
|
contract = updater.getOrCreate(VALIDATOR_EXIT_ADDRESS); |
||||||
|
|
||||||
|
contract.setCode( |
||||||
|
Bytes.fromHexString( |
||||||
|
"0x61013680600a5f395ff33373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b36603014156101325760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061013257600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460ed5780604402838201600302600401805490600101805490600101549160601b8160a01c17835260601b8160a01c17826020015260601b906040015260010160a6565b910180921460fe5790600255610109565b90505f6002555f6003555b5f546001546002828201116101205750505f610126565b01600290035b5f555f6001556044025ff35b5f5ffd")); |
||||||
|
// excess exits
|
||||||
|
contract.setStorageValue(UInt256.valueOf(0), UInt256.valueOf(0)); |
||||||
|
// exits count
|
||||||
|
contract.setStorageValue(UInt256.valueOf(1), UInt256.valueOf(exits.size())); |
||||||
|
// exits queue head pointer
|
||||||
|
contract.setStorageValue(UInt256.valueOf(2), UInt256.valueOf(0)); |
||||||
|
// exits queue tail pointer
|
||||||
|
contract.setStorageValue(UInt256.valueOf(3), UInt256.valueOf(exits.size())); |
||||||
|
|
||||||
|
int offset = 4; |
||||||
|
for (int i = 0; i < exits.size(); i++) { |
||||||
|
final ValidatorExit exit = exits.get(i); |
||||||
|
// source_account
|
||||||
|
contract.setStorageValue( |
||||||
|
// set account to slot, with 12 bytes padding on the left
|
||||||
|
UInt256.valueOf(offset++), |
||||||
|
UInt256.fromBytes( |
||||||
|
Bytes.concatenate( |
||||||
|
Bytes.fromHexString("0x000000000000000000000000"), exit.getSourceAddress()))); |
||||||
|
// validator_pubkey
|
||||||
|
contract.setStorageValue( |
||||||
|
UInt256.valueOf(offset++), UInt256.fromBytes(exit.getValidatorPubKey().slice(0, 32))); |
||||||
|
contract.setStorageValue( |
||||||
|
// set public key to slot, with 16 bytes padding on the right
|
||||||
|
UInt256.valueOf(offset++), |
||||||
|
UInt256.fromBytes( |
||||||
|
Bytes.concatenate( |
||||||
|
exit.getValidatorPubKey().slice(32, 16), |
||||||
|
Bytes.fromHexString("0x00000000000000000000000000000000")))); |
||||||
|
} |
||||||
|
updater.commit(); |
||||||
|
} |
||||||
|
|
||||||
|
private ValidatorExit createExit() { |
||||||
|
return new ValidatorExit( |
||||||
|
Address.extract(Bytes32.random()), BLSPublicKey.wrap(Bytes48.random())); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
/* |
||||||
|
* 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(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,155 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue