Make QBFT validator smart contract mode work with london fork (#5277)

Override the transactionSimulator's default TransactionValidationParams with one that allows for exceeding the account balance (which effectively zeros the baseFee).
This mimics the way that eth_estimateGas and eth_call are implemented.

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
pull/5196/head
Simon Dudley 2 years ago committed by GitHub
parent d3be9c87a6
commit 1b29f686e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 14
      config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java
  3. 25
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java
  4. 46
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java
  5. 46
      consensus/qbft/src/integration-test/resources/genesis_validator_contract_london.json
  6. 14
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java
  7. 48
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java
  8. 2
      ethereum/referencetests/src/reference-test/external-resources

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

@ -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.
*

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

@ -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<QbftFork> qbftForks =

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

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

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

@ -1 +1 @@
Subproject commit bac70c50a579197af68af5fc6d8c7b6163b92c52
Subproject commit 291118cf69f33a4a89f2f61c7bf5fe0e62c9c2f8
Loading…
Cancel
Save