EIP-7002: Validator Exit contract helper and adding exits to created blocks (#6883)

Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com>
pull/6964/head
Lucas Saldanha 7 months ago committed by GitHub
parent f68db3801b
commit 61432831d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 18
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/genesis.json
  2. 8
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/01_cancun_prepare_payload.json
  3. 8
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/02_cancun_getPayloadV3.json
  4. 8
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/03_cancun_newPayloadV3.json
  5. 8
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/04_cancun_forkchoiceUpdatedV3.json
  6. 10
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/05_prague_forkchoiceUpdatedV3.json
  7. 15
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/06_prague_getPayloadV4.json
  8. 23
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/09_prague_newPayloadV4.json
  9. 10
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/10_prague_forkchoiceUpdatedV3.json
  10. 8
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/11_prague_getPayloadV4.json
  11. 42
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/12_cancun_newPayloadV4.json
  12. 14
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/13_prague_send_raw_transaction_create_exit.json
  13. 34
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/14_prague_forkchoiceUpdatedV3.json
  14. 54
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/15_prague_getPayloadV4.json
  15. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV4.java
  16. 5
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4Test.java
  17. 8
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java
  18. 6
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java
  19. 13
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java
  20. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java
  21. 85
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PragueValidatorExitsValidator.java
  22. 146
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ValidatorExitContractHelper.java
  23. 36
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ValidatorExitsValidator.java
  24. 3
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java
  25. 3
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java
  26. 54
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidatorTest.java
  27. 2
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java
  28. 81
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueValidatorExitsValidatorTest.java
  29. 197
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidatorExitContractHelperTest.java
  30. 77
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidatorExitsValidatorTest.java
  31. 155
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidatorExitsValidatorTestFixtures.java
  32. 2
      evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java

File diff suppressed because one or more lines are too long

@ -4,8 +4,8 @@
"method": "engine_forkchoiceUpdatedV3", "method": "engine_forkchoiceUpdatedV3",
"params": [ "params": [
{ {
"headBlockHash": "0x78a301e0d846bd169889c9755c9aa4ce2972dfc4bd63de61f3303887d3e81f98", "headBlockHash": "0x4202d36ad886b24b9bb8c2451217884925577755ae053c8202ef4737134f3ae9",
"safeBlockHash": "0x78a301e0d846bd169889c9755c9aa4ce2972dfc4bd63de61f3303887d3e81f98", "safeBlockHash": "0x4202d36ad886b24b9bb8c2451217884925577755ae053c8202ef4737134f3ae9",
"finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" "finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}, },
{ {
@ -24,10 +24,10 @@
"result": { "result": {
"payloadStatus": { "payloadStatus": {
"status": "VALID", "status": "VALID",
"latestValidHash": "0x78a301e0d846bd169889c9755c9aa4ce2972dfc4bd63de61f3303887d3e81f98", "latestValidHash": "0x4202d36ad886b24b9bb8c2451217884925577755ae053c8202ef4737134f3ae9",
"validationError": null "validationError": null
}, },
"payloadId": "0x282643d459a6f711" "payloadId": "0x2826439412796511"
} }
}, },
"statusCode": 200 "statusCode": 200

@ -3,7 +3,7 @@
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "engine_getPayloadV3", "method": "engine_getPayloadV3",
"params": [ "params": [
"0x282643d459a6f711" "0x2826439412796511"
], ],
"id": 67 "id": 67
}, },
@ -12,9 +12,9 @@
"id": 67, "id": 67,
"result": { "result": {
"executionPayload": { "executionPayload": {
"parentHash": "0x78a301e0d846bd169889c9755c9aa4ce2972dfc4bd63de61f3303887d3e81f98", "parentHash": "0x4202d36ad886b24b9bb8c2451217884925577755ae053c8202ef4737134f3ae9",
"feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"stateRoot": "0x9b8c4a9a86cb49252075c0db2f0e72fb1e49350a0f70ea36f26f700201961e62", "stateRoot": "0xaddb5efeb344ec083c36c46c789e48e6509d82d754aaf4830ca4a51f5c904d84",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x1c9c380", "gasLimit": "0x1c9c380",
@ -29,7 +29,7 @@
"blockNumber": "0x1", "blockNumber": "0x1",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"blobGasUsed": "0x0", "blobGasUsed": "0x0",
"blockHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315" "blockHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331"
}, },
"blockValue": "0x0", "blockValue": "0x0",
"blobsBundle": { "blobsBundle": {

@ -4,9 +4,9 @@
"method": "engine_newPayloadV3", "method": "engine_newPayloadV3",
"params": [ "params": [
{ {
"parentHash": "0x78a301e0d846bd169889c9755c9aa4ce2972dfc4bd63de61f3303887d3e81f98", "parentHash": "0x4202d36ad886b24b9bb8c2451217884925577755ae053c8202ef4737134f3ae9",
"feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"stateRoot": "0x9b8c4a9a86cb49252075c0db2f0e72fb1e49350a0f70ea36f26f700201961e62", "stateRoot": "0xaddb5efeb344ec083c36c46c789e48e6509d82d754aaf4830ca4a51f5c904d84",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x1c9c380", "gasLimit": "0x1c9c380",
@ -17,7 +17,7 @@
"transactions": [], "transactions": [],
"withdrawals": [], "withdrawals": [],
"blockNumber": "0x1", "blockNumber": "0x1",
"blockHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "blockHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"excessBlobGas": "0x0", "excessBlobGas": "0x0",
"blobGasUsed": "0x0" "blobGasUsed": "0x0"
@ -32,7 +32,7 @@
"id": 67, "id": 67,
"result": { "result": {
"status": "VALID", "status": "VALID",
"latestValidHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "latestValidHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"validationError": null "validationError": null
} }
}, },

@ -4,9 +4,9 @@
"method": "engine_forkchoiceUpdatedV3", "method": "engine_forkchoiceUpdatedV3",
"params": [ "params": [
{ {
"headBlockHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "headBlockHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"safeBlockHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "safeBlockHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"finalizedBlockHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315" "finalizedBlockHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331"
}, },
null null
], ],
@ -18,7 +18,7 @@
"result": { "result": {
"payloadStatus": { "payloadStatus": {
"status": "VALID", "status": "VALID",
"latestValidHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "latestValidHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"validationError": null "validationError": null
}, },
"payloadId": null "payloadId": null

@ -4,9 +4,9 @@
"method": "engine_forkchoiceUpdatedV3", "method": "engine_forkchoiceUpdatedV3",
"params": [ "params": [
{ {
"headBlockHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "headBlockHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"safeBlockHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "safeBlockHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" "finalizedBlockHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331"
}, },
{ {
"timestamp": "0x20", "timestamp": "0x20",
@ -24,10 +24,10 @@
"result": { "result": {
"payloadStatus": { "payloadStatus": {
"status": "VALID", "status": "VALID",
"latestValidHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "latestValidHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"validationError": null "validationError": null
}, },
"payloadId": "0x282643b909febddf" "payloadId": "0x282643d6e5fecedf"
} }
}, },
"statusCode": 200 "statusCode": 200

@ -3,7 +3,7 @@
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "engine_getPayloadV4", "method": "engine_getPayloadV4",
"params": [ "params": [
"0x282643b909febddf" "0x282643d6e5fecedf"
], ],
"id": 67 "id": 67
}, },
@ -12,9 +12,9 @@
"id": 67, "id": 67,
"result": { "result": {
"executionPayload": { "executionPayload": {
"parentHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "parentHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"stateRoot": "0x9b8c4a9a86cb49252075c0db2f0e72fb1e49350a0f70ea36f26f700201961e62", "stateRoot": "0xa194d7c0cff95750c211567fba96e394faa89644a661dd1c1b75426dc90728e2",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x1c9c380", "gasLimit": "0x1c9c380",
@ -27,9 +27,14 @@
"transactions": [], "transactions": [],
"withdrawals": [], "withdrawals": [],
"depositReceipts": [], "depositReceipts": [],
"exits": [], "exits": [
{
"sourceAddress": "0xa4664c40aacebd82a2db79f0ea36c06bc6a19adb",
"validatorPubKey": "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"
}
],
"blockNumber": "0x2", "blockNumber": "0x2",
"blockHash": "0x194d190af2a85c418947fecca405eb168c832481f33f618b0c36326ba65d4767", "blockHash": "0x8494d3fc0fd54898100ffa2bda4c3ffdfb9faa3a4c2b2c1638970721e646c35b",
"blobGasUsed": "0x0", "blobGasUsed": "0x0",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
}, },

@ -4,9 +4,9 @@
"method": "engine_newPayloadV4", "method": "engine_newPayloadV4",
"params": [ "params": [
{ {
"parentHash": "0x1dd4f141551d53ce393845e2873754e43396101a8ebc0fd0eeb2e6798a591315", "parentHash": "0xd93892197c5bd2130e6167edc292a1e92021bb9f292e6460aa889c3e2f972331",
"feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"stateRoot": "0x14208ac0e218167936e220b72d5d5887a963cb858ea2f2d268518f014a3da3fa", "stateRoot": "0xd5d6e8c8d57e328871c5b81f078ab69e02466ab0e487c2c597effb4ffc185384",
"logsBloom": "0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000", "logsBloom": "0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x1c9c380", "gasLimit": "0x1c9c380",
@ -20,11 +20,22 @@
], ],
"withdrawals": [], "withdrawals": [],
"depositReceipts": [ "depositReceipts": [
{"amount":"0x773594000","index":"0x0","pubkey":"0x96a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9","signature":"0xb1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9","withdrawalCredentials":"0x003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef2"} {
"amount": "0x773594000",
"index": "0x0",
"pubkey": "0x96a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9",
"signature": "0xb1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9",
"withdrawalCredentials": "0x003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef2"
}
],
"exits": [
{
"sourceAddress": "0xa4664c40aacebd82a2db79f0ea36c06bc6a19adb",
"validatorPubKey": "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"
}
], ],
"exits": [],
"blockNumber": "0x2", "blockNumber": "0x2",
"blockHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "blockHash": "0x2b3ae3a4c482f3dab43f0606af50dc8fd3ab981ba0659d477fa96955927736ae",
"receiptsRoot": "0x79ee3424eb720a3ad4b1c5a372bb8160580cbe4d893778660f34213c685627a9", "receiptsRoot": "0x79ee3424eb720a3ad4b1c5a372bb8160580cbe4d893778660f34213c685627a9",
"blobGasUsed": "0x0" "blobGasUsed": "0x0"
}, },
@ -38,7 +49,7 @@
"id": 67, "id": 67,
"result": { "result": {
"status": "VALID", "status": "VALID",
"latestValidHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "latestValidHash": "0x2b3ae3a4c482f3dab43f0606af50dc8fd3ab981ba0659d477fa96955927736ae",
"validationError": null "validationError": null
} }
}, },

@ -4,9 +4,9 @@
"method": "engine_forkchoiceUpdatedV3", "method": "engine_forkchoiceUpdatedV3",
"params": [ "params": [
{ {
"headBlockHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "headBlockHash": "0x2b3ae3a4c482f3dab43f0606af50dc8fd3ab981ba0659d477fa96955927736ae",
"safeBlockHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "safeBlockHash": "0x2b3ae3a4c482f3dab43f0606af50dc8fd3ab981ba0659d477fa96955927736ae",
"finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" "finalizedBlockHash": "0x2b3ae3a4c482f3dab43f0606af50dc8fd3ab981ba0659d477fa96955927736ae"
}, },
{ {
"timestamp": "0x30", "timestamp": "0x30",
@ -24,10 +24,10 @@
"result": { "result": {
"payloadStatus": { "payloadStatus": {
"status": "VALID", "status": "VALID",
"latestValidHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "latestValidHash": "0x2b3ae3a4c482f3dab43f0606af50dc8fd3ab981ba0659d477fa96955927736ae",
"validationError": null "validationError": null
}, },
"payloadId": "0x282643f559414ecf" "payloadId": "0x2826439bac38c031"
} }
}, },
"statusCode" : 200 "statusCode" : 200

@ -3,7 +3,7 @@
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "engine_getPayloadV4", "method": "engine_getPayloadV4",
"params": [ "params": [
"0x282643f559414ecf" "0x2826439bac38c031"
], ],
"id": 67 "id": 67
}, },
@ -12,9 +12,9 @@
"id": 67, "id": 67,
"result": { "result": {
"executionPayload": { "executionPayload": {
"parentHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "parentHash": "0x2b3ae3a4c482f3dab43f0606af50dc8fd3ab981ba0659d477fa96955927736ae",
"feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"stateRoot": "0x14208ac0e218167936e220b72d5d5887a963cb858ea2f2d268518f014a3da3fa", "stateRoot": "0xd5d6e8c8d57e328871c5b81f078ab69e02466ab0e487c2c597effb4ffc185384",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x1c9c380", "gasLimit": "0x1c9c380",
@ -29,7 +29,7 @@
"depositReceipts": [], "depositReceipts": [],
"exits": [], "exits": [],
"blockNumber": "0x3", "blockNumber": "0x3",
"blockHash": "0xec4741580be2d83cde0dcd6346a67a71636d915f5da592f39d4470aecef72020", "blockHash": "0x0bd5e56ac3552719a1af061ec3f48248e817fc8ac7306d611d195ae023e9f771",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"blobGasUsed": "0x0" "blobGasUsed": "0x0"
}, },

@ -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
}

@ -132,8 +132,8 @@ public class EngineGetPayloadResultV4 {
this.exits = this.exits =
exits exits
.map( .map(
ds -> exit ->
ds.stream() exit.stream()
.map(ValidatorExitParameter::fromValidatorExit) .map(ValidatorExitParameter::fromValidatorExit)
.collect(Collectors.toList())) .collect(Collectors.toList()))
.orElse(null); .orElse(null);

@ -44,6 +44,7 @@ import org.hyperledger.besu.ethereum.core.ValidatorExit;
import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.mainnet.BodyValidation; import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
import org.hyperledger.besu.ethereum.mainnet.DepositsValidator; import org.hyperledger.besu.ethereum.mainnet.DepositsValidator;
import org.hyperledger.besu.ethereum.mainnet.PragueValidatorExitsValidator;
import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidator; import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidator;
import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator;
@ -196,7 +197,7 @@ public class EngineNewPayloadV4Test extends EngineNewPayloadV3Test {
@Test @Test
public void shouldReturnInvalidIfExitsIsNull_WhenExitsAllowed() { public void shouldReturnInvalidIfExitsIsNull_WhenExitsAllowed() {
when(protocolSpec.getExitsValidator()).thenReturn(new ValidatorExitsValidator.AllowedExits()); when(protocolSpec.getExitsValidator()).thenReturn(new PragueValidatorExitsValidator());
var resp = var resp =
resp( resp(
@ -216,7 +217,7 @@ public class EngineNewPayloadV4Test extends EngineNewPayloadV3Test {
final List<ValidatorExitParameter> validatorExitsParams = List.of(VALIDATOR_EXIT_PARAMETER_1); final List<ValidatorExitParameter> validatorExitsParams = List.of(VALIDATOR_EXIT_PARAMETER_1);
final List<ValidatorExit> validatorExits = final List<ValidatorExit> validatorExits =
List.of(VALIDATOR_EXIT_PARAMETER_1.toValidatorExit()); List.of(VALIDATOR_EXIT_PARAMETER_1.toValidatorExit());
when(protocolSpec.getExitsValidator()).thenReturn(new ValidatorExitsValidator.AllowedExits()); when(protocolSpec.getExitsValidator()).thenReturn(new PragueValidatorExitsValidator());
BlockHeader mockHeader = BlockHeader mockHeader =
setupValidPayload( setupValidPayload(

@ -50,6 +50,7 @@ import org.hyperledger.besu.ethereum.mainnet.ParentBeaconBlockRootHelper;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.mainnet.ValidatorExitContractHelper;
import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidator; import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidator;
import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor; import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor;
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
@ -255,12 +256,11 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
throwIfStopped(); throwIfStopped();
// TODO implement logic to retrieve validator exits from precompile
// https://github.com/hyperledger/besu/issues/6800
final ValidatorExitsValidator exitsValidator = newProtocolSpec.getExitsValidator(); final ValidatorExitsValidator exitsValidator = newProtocolSpec.getExitsValidator();
Optional<List<ValidatorExit>> maybeExits = Optional.empty(); Optional<List<ValidatorExit>> maybeExits = Optional.empty();
if (exitsValidator instanceof ValidatorExitsValidator.AllowedExits) { if (exitsValidator.allowValidatorExits()) {
maybeExits = Optional.of(List.of()); maybeExits =
Optional.of(ValidatorExitContractHelper.popExitsFromQueue(disposableWorldState));
} }
throwIfStopped(); throwIfStopped();

@ -197,6 +197,12 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
} }
} }
final ValidatorExitsValidator exitsValidator = protocolSpec.getExitsValidator();
if (exitsValidator.allowValidatorExits()) {
// Performing system-call logic
ValidatorExitContractHelper.popExitsFromQueue(worldState);
}
if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) { if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) {
// no need to log, rewardCoinbase logs the error. // no need to log, rewardCoinbase logs the error.
if (worldState instanceof BonsaiWorldState) { if (worldState instanceof BonsaiWorldState) {

@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.core.ValidatorExit;
import org.hyperledger.besu.evm.log.LogsBloomFilter; import org.hyperledger.besu.evm.log.LogsBloomFilter;
import java.util.HashSet; import java.util.HashSet;
@ -108,6 +109,12 @@ public class MainnetBlockBodyValidator implements BlockBodyValidator {
return false; return false;
} }
if (body.getExits().isPresent()) {
if (!validateExits(block, body.getExits().get())) {
return false;
}
}
return true; return true;
} }
@ -323,4 +330,10 @@ public class MainnetBlockBodyValidator implements BlockBodyValidator {
return true; return true;
} }
private boolean validateExits(final Block block, final List<ValidatorExit> exits) {
final ValidatorExitsValidator exitsValidator =
protocolSchedule.getByBlockHeader(block.getHeader()).getExitsValidator();
return exitsValidator.validateExitsInBlock(block, exits);
}
} }

@ -764,7 +764,7 @@ public abstract class MainnetProtocolSpecs {
// use prague precompiled contracts // use prague precompiled contracts
.precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::prague) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::prague)
.depositsValidator(new DepositsValidator.AllowedDeposits(depositContractAddress)) .depositsValidator(new DepositsValidator.AllowedDeposits(depositContractAddress))
.exitsValidator(new ValidatorExitsValidator.AllowedExits()) .exitsValidator(new PragueValidatorExitsValidator())
.name("Prague"); .name("Prague");
} }

@ -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));
}
}

@ -16,18 +16,33 @@
package org.hyperledger.besu.ethereum.mainnet; package org.hyperledger.besu.ethereum.mainnet;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.ValidatorExit;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public interface ValidatorExitsValidator { public interface ValidatorExitsValidator {
boolean allowValidatorExits();
boolean validateValidatorExitParameter(Optional<List<ValidatorExit>> validatorExits); boolean validateValidatorExitParameter(Optional<List<ValidatorExit>> validatorExits);
boolean validateExitsInBlock(Block block, List<ValidatorExit> validatorExits);
/** Used before Prague */ /** Used before Prague */
class ProhibitedExits implements ValidatorExitsValidator { 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 * Before Prague we do not expect to have execution layer triggered exits, so it is expected the
* optional parameter will be empty * optional parameter will be empty
@ -40,17 +55,22 @@ public interface ValidatorExitsValidator {
final Optional<List<ValidatorExit>> validatorExits) { final Optional<List<ValidatorExit>> validatorExits) {
return validatorExits.isEmpty(); 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;
} }
/** Used after Prague */ if (block.getHeader().getExitsRoot().isPresent()) {
class AllowedExits implements ValidatorExitsValidator { LOG.warn("Block {} header contains exits_root but exits are prohibited", block.getHash());
return false;
}
@Override return true;
public boolean validateValidatorExitParameter(
final Optional<List<ValidatorExit>> validatorExits) {
// TODO implement any extra required validation (see
// https://github.com/hyperledger/besu/issues/6800)
return validatorExits.isPresent();
} }
} }
} }

@ -40,6 +40,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetBlockProcessor;
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidator;
import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
@ -109,6 +110,8 @@ class BlockImportExceptionHandlingTest {
when(protocolContext.getBlockchain()).thenReturn(blockchain); when(protocolContext.getBlockchain()).thenReturn(blockchain);
when(protocolContext.getWorldStateArchive()).thenReturn(worldStateArchive); when(protocolContext.getWorldStateArchive()).thenReturn(worldStateArchive);
when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
when(protocolSpec.getExitsValidator())
.thenReturn(new ValidatorExitsValidator.ProhibitedExits());
mainnetBlockValidator = mainnetBlockValidator =
new MainnetBlockValidator( new MainnetBlockValidator(
blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager); blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager);

@ -66,6 +66,9 @@ abstract class AbstractBlockProcessorTest {
@BeforeEach @BeforeEach
void baseSetup() { void baseSetup() {
lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
lenient()
.when(protocolSpec.getExitsValidator())
.thenReturn(new ValidatorExitsValidator.ProhibitedExits());
blockProcessor = blockProcessor =
new TestBlockProcessor( new TestBlockProcessor(
transactionProcessor, transactionProcessor,

@ -18,6 +18,7 @@ import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.NONE; import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.NONE;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Address;
@ -34,6 +35,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.apache.tuweni.units.bigints.UInt64; import org.apache.tuweni.units.bigints.UInt64;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
@ -50,6 +52,23 @@ class MainnetBlockBodyValidatorTest {
@Mock private ProtocolSpec protocolSpec; @Mock private ProtocolSpec protocolSpec;
@Mock private WithdrawalsValidator withdrawalsValidator; @Mock private WithdrawalsValidator withdrawalsValidator;
@Mock private DepositsValidator depositsValidator; @Mock private DepositsValidator depositsValidator;
@Mock private ValidatorExitsValidator exitsValidator;
@BeforeEach
public void setUp() {
lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
lenient().when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator);
lenient().when(withdrawalsValidator.validateWithdrawals(any())).thenReturn(true);
lenient().when(withdrawalsValidator.validateWithdrawalsRoot(any())).thenReturn(true);
lenient().when(protocolSpec.getDepositsValidator()).thenReturn(depositsValidator);
lenient().when(depositsValidator.validateDeposits(any(), any())).thenReturn(true);
lenient().when(depositsValidator.validateDepositsRoot(any())).thenReturn(true);
lenient().when(protocolSpec.getExitsValidator()).thenReturn(exitsValidator);
lenient().when(exitsValidator.validateExitsInBlock(any(), any())).thenReturn(true);
}
@Test @Test
void validatesWithdrawals() { void validatesWithdrawals() {
@ -67,13 +86,7 @@ class MainnetBlockBodyValidatorTest {
.setWithdrawalsRoot(BodyValidation.withdrawalsRoot(withdrawals))); .setWithdrawalsRoot(BodyValidation.withdrawalsRoot(withdrawals)));
blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList()); blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList());
when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator);
when(protocolSpec.getDepositsValidator()).thenReturn(depositsValidator);
when(withdrawalsValidator.validateWithdrawals(Optional.of(withdrawals))).thenReturn(true); when(withdrawalsValidator.validateWithdrawals(Optional.of(withdrawals))).thenReturn(true);
when(withdrawalsValidator.validateWithdrawalsRoot(block)).thenReturn(true);
when(depositsValidator.validateDeposits(any(), any())).thenReturn(true);
when(depositsValidator.validateDepositsRoot(block)).thenReturn(true);
assertThat( assertThat(
new MainnetBlockBodyValidator(protocolSchedule) new MainnetBlockBodyValidator(protocolSchedule)
@ -97,8 +110,6 @@ class MainnetBlockBodyValidatorTest {
.setWithdrawalsRoot(BodyValidation.withdrawalsRoot(withdrawals))); .setWithdrawalsRoot(BodyValidation.withdrawalsRoot(withdrawals)));
blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList()); blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList());
when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator);
when(withdrawalsValidator.validateWithdrawals(Optional.empty())).thenReturn(false); when(withdrawalsValidator.validateWithdrawals(Optional.empty())).thenReturn(false);
assertThat( assertThat(
@ -123,9 +134,6 @@ class MainnetBlockBodyValidatorTest {
.setWithdrawals(Optional.of(withdrawals))); .setWithdrawals(Optional.of(withdrawals)));
blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList()); blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList());
when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator);
when(withdrawalsValidator.validateWithdrawals(Optional.of(withdrawals))).thenReturn(true);
when(withdrawalsValidator.validateWithdrawalsRoot(block)).thenReturn(false); when(withdrawalsValidator.validateWithdrawalsRoot(block)).thenReturn(false);
assertThat( assertThat(
@ -134,4 +142,28 @@ class MainnetBlockBodyValidatorTest {
blockchainSetupUtil.getProtocolContext(), block, emptyList(), NONE)) blockchainSetupUtil.getProtocolContext(), block, emptyList(), NONE))
.isFalse(); .isFalse();
} }
@Test
public void validationFailsIfExitsValidationFails() {
final Block block =
blockDataGenerator.block(
new BlockOptions()
.setBlockNumber(1)
.setGasUsed(0)
.hasTransactions(false)
.hasOmmers(false)
.setReceiptsRoot(BodyValidation.receiptsRoot(emptyList()))
.setLogsBloom(LogsBloomFilter.empty())
.setParentHash(blockchainSetupUtil.getBlockchain().getChainHeadHash())
.setExits(Optional.of(List.of())));
blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList());
when(exitsValidator.validateExitsInBlock(any(), any())).thenReturn(false);
assertThat(
new MainnetBlockBodyValidator(protocolSchedule)
.validateBodyLight(
blockchainSetupUtil.getProtocolContext(), block, emptyList(), NONE))
.isFalse();
}
} }

@ -48,6 +48,8 @@ public class MainnetBlockProcessorTest extends AbstractBlockProcessorTest {
@BeforeEach @BeforeEach
public void setup() { public void setup() {
when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
when(protocolSpec.getExitsValidator())
.thenReturn(new ValidatorExitsValidator.ProhibitedExits());
} }
@Test @Test

@ -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;
}
}
}

@ -163,8 +163,6 @@ public interface MainnetPrecompiledContracts {
populateForCancun(registry, gasCalculator); populateForCancun(registry, gasCalculator);
// TODO: add Prague precompiles here // TODO: add Prague precompiles here
// EIP-7002 - Execution layer triggerable exits
// (https://github.com/hyperledger/besu/issues/6800)
} }
/** /**

Loading…
Cancel
Save