diff --git a/CHANGELOG.md b/CHANGELOG.md index ceb3914a1..1b1e9ea15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ ### Bug Fixes - Fix QBFT and IBFT unable to propose blocks on London when zeroBaseFee is used [#5276](https://github.com/hyperledger/besu/pull/5276) +- Make QBFT validator smart contract mode work with london fork [#5249](https://github.com/hyperledger/besu/issues/5249) + ### Download Links ## 23.1.2 diff --git a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java index 5f87f8640..d391e0410 100644 --- a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java @@ -75,6 +75,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable private BftConfigOptions bftConfigOptions = JsonBftConfigOptions.DEFAULT; private TransitionsConfigOptions transitions = TransitionsConfigOptions.DEFAULT; private static final DiscoveryOptions DISCOVERY_OPTIONS = DiscoveryOptions.DEFAULT; + private boolean zeroBaseFee = false; @Override public StubGenesisConfigOptions clone() { @@ -439,7 +440,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable @Override public boolean isZeroBaseFee() { - return false; + return zeroBaseFee; } @Override @@ -695,6 +696,17 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable return this; } + /** + * Zero base fee per gas stub genesis config options. + * + * @param zeroBaseFee the zero base fee override + * @return the stub genesis config options + */ + public StubGenesisConfigOptions zeroBaseFee(final boolean zeroBaseFee) { + this.zeroBaseFee = zeroBaseFee; + return this; + } + /** * Classic fork block stub genesis config options. * diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index 87c9e4900..6dfb03791 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -125,6 +125,8 @@ public class TestContextBuilder { private static final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private boolean useValidatorContract; + private boolean useLondonMilestone = false; + private boolean useZeroBaseFee = false; private static class ControllerAndState { @@ -237,6 +239,16 @@ public class TestContextBuilder { return this; } + public TestContextBuilder useLondonMilestone(final boolean useLondonMilestone) { + this.useLondonMilestone = useLondonMilestone; + return this; + } + + public TestContextBuilder useZeroBaseFee(final boolean useZeroBaseFee) { + this.useZeroBaseFee = useZeroBaseFee; + return this; + } + public TestContextBuilder qbftForks(final List qbftForks) { this.qbftForks = qbftForks; return this; @@ -301,6 +313,8 @@ public class TestContextBuilder { gossiper, synchronizerUpdater, useValidatorContract, + useLondonMilestone, + useZeroBaseFee, qbftForks); // Add each networkNode to the Multicaster (such that each can receive msgs from local node). @@ -379,6 +393,8 @@ public class TestContextBuilder { final Gossiper gossiper, final SynchronizerUpdater synchronizerUpdater, final boolean useValidatorContract, + final boolean useLondonMilestone, + final boolean useZeroBaseFee, final List qbftForks) { final MiningParameters miningParams = @@ -398,7 +414,14 @@ public class TestContextBuilder { : Collections.emptyMap(); final QbftConfigOptions qbftConfigOptions = createGenesisConfig(useValidatorContract); - genesisConfigOptions.byzantiumBlock(0); + if (useLondonMilestone) { + genesisConfigOptions.londonBlock(0); + } else { + genesisConfigOptions.berlinBlock(0); + } + if (useZeroBaseFee) { + genesisConfigOptions.zeroBaseFee(true); + } genesisConfigOptions.qbftConfigOptions( new JsonQbftConfigOptions(JsonUtil.objectNodeFromMap(qbftConfigValues))); genesisConfigOptions.transitions(TestTransitions.createQbftTestTransitions(qbftForks)); diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java index eec0a01d4..705ebb933 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java @@ -94,6 +94,52 @@ public class ValidatorContractTest { assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS); } + @Test + public void retrievesValidatorsFromValidatorContract_LondonFork() { + final TestContext context = + new TestContextBuilder() + .indexOfFirstLocallyProposedBlock(0) + .nodeParams( + List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)))) + .clock(TestClock.fixed()) + .genesisFile(Resources.getResource("genesis_validator_contract_london.json").getFile()) + .useValidatorContract(true) + .useLondonMilestone(true) + .buildAndStart(); + + createNewBlockAsProposer(context, 1); + + final ValidatorProvider validatorProvider = context.getValidatorProvider(); + final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); + final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get(); + assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).containsExactly(NODE_ADDRESS); + assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS); + } + + @Test + public void retrievesValidatorsFromValidatorContract_LondonFork_ZeroBaseFee() { + // Using London on a free gas network + final TestContext context = + new TestContextBuilder() + .indexOfFirstLocallyProposedBlock(0) + .nodeParams( + List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)))) + .clock(TestClock.fixed()) + .genesisFile(Resources.getResource("genesis_validator_contract_london.json").getFile()) + .useValidatorContract(true) + .useLondonMilestone(true) + .useZeroBaseFee(true) + .buildAndStart(); + + createNewBlockAsProposer(context, 1); + + final ValidatorProvider validatorProvider = context.getValidatorProvider(); + final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); + final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get(); + assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).containsExactly(NODE_ADDRESS); + assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS); + } + @Test public void transitionsFromBlockHeaderModeToValidatorContractMode() { final List qbftForks = diff --git a/consensus/qbft/src/integration-test/resources/genesis_validator_contract_london.json b/consensus/qbft/src/integration-test/resources/genesis_validator_contract_london.json new file mode 100644 index 000000000..e5b178f3a --- /dev/null +++ b/consensus/qbft/src/integration-test/resources/genesis_validator_contract_london.json @@ -0,0 +1,46 @@ +{ + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0xe5a00000000000000000000000000000000000000000000000000000000000000000c0c080c0", + "gasLimit": "0x29b92700", + "difficulty": "0x1", + "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "64d9be4177f418bcf4e56adad85f33e3a64efe22": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "9f66f8a0f0a6537e4a36aa1799673ea7ae97a166": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "a7f25969fb6f3d5ac09a88862c90b5ff664557a7": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "f4bbfd32c11c9d63e9b4c77bb225810f840342df": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "0x0000000000000000000000000000000000008888": { + "comment": "validator smart contract. This is compiled from validator_contract.sol using solc --evm-version byzantium --bin-runtime validator_contract.sol", + "balance": "0", + "code": "608060405234801561001057600080fd5b5060043610610048576000357c010000000000000000000000000000000000000000000000000000000090048063b7ab4db51461004d575b600080fd5b61005561006b565b604051610062919061017e565b60405180910390f35b606060008054806020026020016040519081016040528092919081815260200182805480156100ef57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116100a5575b5050505050905090565b60006101058383610111565b60208301905092915050565b61011a816101d9565b82525050565b600061012b826101b0565b61013581856101c8565b9350610140836101a0565b8060005b8381101561017157815161015888826100f9565b9750610163836101bb565b925050600181019050610144565b5085935050505092915050565b600060208201905081810360008301526101988184610120565b905092915050565b6000819050602082019050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b60006101e4826101eb565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff8216905091905056fea26469706673582212206d880cf012c1677c691bf6f2f0a0e4eadf57866ffe5cd2d9833d3cfdf27b15f664736f6c63430008060033", + "storage": { + "0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000001", + "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df" + } + }, + "0x0000000000000000000000000000000000009999": { + "comment": "validator smart contract. This is compiled from validator_contract.sol using solc --evm-version byzantium --bin-runtime validator_contract.sol", + "balance": "0", + "code": "608060405234801561001057600080fd5b5060043610610048576000357c010000000000000000000000000000000000000000000000000000000090048063b7ab4db51461004d575b600080fd5b61005561006b565b604051610062919061017e565b60405180910390f35b606060008054806020026020016040519081016040528092919081815260200182805480156100ef57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116100a5575b5050505050905090565b60006101058383610111565b60208301905092915050565b61011a816101d9565b82525050565b600061012b826101b0565b61013581856101c8565b9350610140836101a0565b8060005b8381101561017157815161015888826100f9565b9750610163836101bb565b925050600181019050610144565b5085935050505092915050565b600060208201905081810360008301526101988184610120565b905092915050565b6000819050602082019050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b60006101e4826101eb565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff8216905091905056fea26469706673582212206d880cf012c1677c691bf6f2f0a0e4eadf57866ffe5cd2d9833d3cfdf27b15f664736f6c63430008060033", + "storage": { + "0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000002", + "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "000000000000000000000000e98d92560fac3069ccff53ef348ded26a51d4b68", + "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df" + } + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": "0x7" +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java index 01c98bca4..a7b705e4a 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java @@ -15,9 +15,12 @@ package org.hyperledger.besu.consensus.qbft.validator; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; +import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.Collection; import java.util.List; @@ -89,7 +92,13 @@ public class ValidatorContractController { final Bytes payload = Bytes.fromHexString(FunctionEncoder.encode(function)); final CallParameter callParams = new CallParameter(null, contractAddress, -1, null, null, payload); - return transactionSimulator.process(callParams, blockNumber); + final TransactionValidationParams transactionValidationParams = + ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .isAllowExceedingBalance(true) + .build(); + return transactionSimulator.process( + callParams, transactionValidationParams, OperationTracer.NO_TRACING, blockNumber); } @SuppressWarnings("rawtypes") @@ -107,7 +116,8 @@ public class ValidatorContractController { return decodedList; } else { - throw new IllegalStateException("Failed validator smart contract call"); + throw new IllegalStateException( + "Failed validator smart contract call: " + result.getValidationResult()); } } } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java index 4dc848172..4702a4eee 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java @@ -22,12 +22,15 @@ import org.hyperledger.besu.config.JsonQbftConfigOptions; import org.hyperledger.besu.consensus.qbft.MutableQbftConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; +import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.Collection; import java.util.List; @@ -49,9 +52,15 @@ public class ValidatorContractControllerTest { private static final Address VALIDATOR_ADDRESS = Address.fromHexString("0xeac51e3fe1afc9894f0dfeab8ceb471899b932df"); private static final Address CONTRACT_ADDRESS = Address.fromHexString("1"); + private static final TransactionValidationParams ALLOW_EXCEEDING_BALANCE_VALIDATION_PARAMS = + ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .isAllowExceedingBalance(true) + .build(); private final TransactionSimulator transactionSimulator = Mockito.mock(TransactionSimulator.class); + private final Transaction transaction = Mockito.mock(Transaction.class); private CallParameter callParameter; @@ -81,7 +90,12 @@ public class ValidatorContractControllerTest { Bytes.fromHexString(GET_VALIDATORS_FUNCTION_RESULT), ValidationResult.valid())); - when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result)); + when(transactionSimulator.process( + callParameter, + ALLOW_EXCEEDING_BALANCE_VALIDATION_PARAMS, + OperationTracer.NO_TRACING, + 1)) + .thenReturn(Optional.of(result)); final ValidatorContractController validatorContractController = new ValidatorContractController(transactionSimulator); @@ -98,13 +112,18 @@ public class ValidatorContractControllerTest { TransactionProcessingResult.invalid( ValidationResult.invalid(TransactionInvalidReason.INTERNAL_ERROR))); - when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result)); + when(transactionSimulator.process( + callParameter, + ALLOW_EXCEEDING_BALANCE_VALIDATION_PARAMS, + OperationTracer.NO_TRACING, + 1)) + .thenReturn(Optional.of(result)); final ValidatorContractController validatorContractController = new ValidatorContractController(transactionSimulator); Assertions.assertThatThrownBy( () -> validatorContractController.getValidators(1, CONTRACT_ADDRESS)) - .hasMessage("Failed validator smart contract call"); + .hasMessageContaining("Failed validator smart contract call"); } @Test @@ -118,13 +137,18 @@ public class ValidatorContractControllerTest { ValidationResult.invalid(TransactionInvalidReason.INTERNAL_ERROR), Optional.empty())); - when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result)); + when(transactionSimulator.process( + callParameter, + ALLOW_EXCEEDING_BALANCE_VALIDATION_PARAMS, + OperationTracer.NO_TRACING, + 1)) + .thenReturn(Optional.of(result)); final ValidatorContractController validatorContractController = new ValidatorContractController(transactionSimulator); Assertions.assertThatThrownBy( () -> validatorContractController.getValidators(1, CONTRACT_ADDRESS)) - .hasMessage("Failed validator smart contract call"); + .hasMessageContaining("Failed validator smart contract call"); } @Test @@ -135,7 +159,12 @@ public class ValidatorContractControllerTest { TransactionProcessingResult.successful( List.of(), 0, 0, Bytes.EMPTY, ValidationResult.valid())); - when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result)); + when(transactionSimulator.process( + callParameter, + ALLOW_EXCEEDING_BALANCE_VALIDATION_PARAMS, + OperationTracer.NO_TRACING, + 1)) + .thenReturn(Optional.of(result)); final ValidatorContractController validatorContractController = new ValidatorContractController(transactionSimulator); @@ -147,7 +176,12 @@ public class ValidatorContractControllerTest { @Test public void throwErrorIfEmptySimulationResult() { - when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.empty()); + when(transactionSimulator.process( + callParameter, + ALLOW_EXCEEDING_BALANCE_VALIDATION_PARAMS, + OperationTracer.NO_TRACING, + 1)) + .thenReturn(Optional.empty()); final ValidatorContractController validatorContractController = new ValidatorContractController(transactionSimulator); diff --git a/ethereum/referencetests/src/reference-test/external-resources b/ethereum/referencetests/src/reference-test/external-resources index bac70c50a..291118cf6 160000 --- a/ethereum/referencetests/src/reference-test/external-resources +++ b/ethereum/referencetests/src/reference-test/external-resources @@ -1 +1 @@ -Subproject commit bac70c50a579197af68af5fc6d8c7b6163b92c52 +Subproject commit 291118cf69f33a4a89f2f61c7bf5fe0e62c9c2f8