mirror of
https://github.com/vacp2p/status-linea-besu.git
synced 2026-01-08 21:38:15 -05:00
Add new acceptance test to soak test BFT chains (#7023)
* Add new acceptance test to soak test BFT chains Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Spotless Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Tidy up a little with re-usable start and stop functions with built in delays Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Add shanghai version of Simple Storage contract Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Put commented gradle code back in. Fix the web3j example commands in .sol files Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Spotless Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Set publication artifacts to avoid clash Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Exclude from regular acceptance tests Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Add shanghai fork to the test. Stall the chain for less time to reduce the time taken to mine new blocks Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Tidy up Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Update acceptance-tests/tests/shanghai/build.gradle Co-authored-by: Simon Dudley <simon.dudley@consensys.net> Signed-off-by: Matt Whitehead <matthew1001@hotmail.com> * Tidy up var names Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Fix ports for IBFT2 as well as QBFT Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Remove maven publish spec, disable jar building for shanghai contract project Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * web3j version Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Make fixed port optional when creating a BFT node Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> * Only check artifact coordinates for those starting 'org.*' Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> --------- Signed-off-by: Matthew Whitehead <matthew1001@gmail.com> Signed-off-by: Matt Whitehead <matthew1001@hotmail.com> Signed-off-by: Matt Whitehead <matthew.whitehead@kaleido.io> Co-authored-by: Simon Dudley <simon.dudley@consensys.net> Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
This commit is contained in:
@@ -461,16 +461,30 @@ public class BesuNodeFactory {
|
||||
.build());
|
||||
}
|
||||
|
||||
public BesuNode createIbft2Node(final String name) throws IOException {
|
||||
return create(
|
||||
public BesuNode createIbft2Node(final String name, final boolean fixedPort) throws IOException {
|
||||
JsonRpcConfiguration rpcConfig = node.createJsonRpcWithIbft2EnabledConfig(false);
|
||||
rpcConfig.addRpcApi("ADMIN,TXPOOL");
|
||||
if (fixedPort) {
|
||||
rpcConfig.setPort(
|
||||
Math.abs(name.hashCode() % 60000)
|
||||
+ 1024); // Generate a consistent port for p2p based on node name
|
||||
}
|
||||
BesuNodeConfigurationBuilder builder =
|
||||
new BesuNodeConfigurationBuilder()
|
||||
.name(name)
|
||||
.miningEnabled()
|
||||
.jsonRpcConfiguration(node.createJsonRpcWithIbft2EnabledConfig(false))
|
||||
.jsonRpcConfiguration(rpcConfig)
|
||||
.webSocketConfiguration(node.createWebSocketEnabledConfig())
|
||||
.devMode(false)
|
||||
.genesisConfigProvider(GenesisConfigurationFactory::createIbft2GenesisConfig)
|
||||
.build());
|
||||
.genesisConfigProvider(GenesisConfigurationFactory::createIbft2GenesisConfig);
|
||||
if (fixedPort) {
|
||||
builder.p2pPort(
|
||||
Math.abs(name.hashCode() % 60000)
|
||||
+ 1024
|
||||
+ 500); // Generate a consistent port for p2p based on node name (+ 500 to avoid
|
||||
// clashing with RPC port or other nodes with a similar name)
|
||||
}
|
||||
return create(builder.build());
|
||||
}
|
||||
|
||||
public BesuNode createQbftNodeWithTLS(final String name, final String type) throws IOException {
|
||||
@@ -498,16 +512,31 @@ public class BesuNodeFactory {
|
||||
return createQbftNodeWithTLS(name, KeyStoreWrapper.KEYSTORE_TYPE_PKCS11);
|
||||
}
|
||||
|
||||
public BesuNode createQbftNode(final String name) throws IOException {
|
||||
return create(
|
||||
public BesuNode createQbftNode(final String name, final boolean fixedPort) throws IOException {
|
||||
JsonRpcConfiguration rpcConfig = node.createJsonRpcWithQbftEnabledConfig(false);
|
||||
rpcConfig.addRpcApi("ADMIN,TXPOOL");
|
||||
if (fixedPort) {
|
||||
rpcConfig.setPort(
|
||||
Math.abs(name.hashCode() % 60000)
|
||||
+ 1024); // Generate a consistent port for p2p based on node name
|
||||
}
|
||||
|
||||
BesuNodeConfigurationBuilder builder =
|
||||
new BesuNodeConfigurationBuilder()
|
||||
.name(name)
|
||||
.miningEnabled()
|
||||
.jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false))
|
||||
.jsonRpcConfiguration(rpcConfig)
|
||||
.webSocketConfiguration(node.createWebSocketEnabledConfig())
|
||||
.devMode(false)
|
||||
.genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig)
|
||||
.build());
|
||||
.genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig);
|
||||
if (fixedPort) {
|
||||
builder.p2pPort(
|
||||
Math.abs(name.hashCode() % 60000)
|
||||
+ 1024
|
||||
+ 500); // Generate a consistent port for p2p based on node name (+ 500 to avoid
|
||||
// clashing with RPC port or other nodes with a similar name)
|
||||
}
|
||||
return create(builder.build());
|
||||
}
|
||||
|
||||
public BesuNode createCustomGenesisNode(
|
||||
|
||||
@@ -24,6 +24,7 @@ solidity {
|
||||
resolvePackages = false
|
||||
// TODO: remove the forced version, when DEV network is upgraded to support latest forks
|
||||
version '0.8.19'
|
||||
evmVersion 'london'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -79,6 +80,7 @@ dependencies {
|
||||
testImplementation 'org.web3j:besu'
|
||||
testImplementation 'org.web3j:core'
|
||||
testImplementation 'org.wiremock:wiremock'
|
||||
testImplementation project(path: ':acceptance-tests:tests:shanghai')
|
||||
|
||||
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'
|
||||
}
|
||||
@@ -153,6 +155,7 @@ task acceptanceTestMainnet(type: Test) {
|
||||
task acceptanceTestNotPrivacy(type: Test) {
|
||||
inputs.property "integration.date", LocalTime.now() // so it runs at every invocation
|
||||
exclude '**/privacy/**'
|
||||
exclude '**/bftsoak/**'
|
||||
|
||||
useJUnitPlatform {}
|
||||
|
||||
@@ -205,6 +208,35 @@ task acceptanceTestCliqueBft(type: Test) {
|
||||
doFirst { mkdir "${buildDir}/jvmErrorLogs" }
|
||||
}
|
||||
|
||||
task acceptanceTestBftSoak(type: Test) {
|
||||
inputs.property "integration.date", LocalTime.now() // so it runs at every invocation
|
||||
include '**/bftsoak/**'
|
||||
|
||||
useJUnitPlatform {}
|
||||
|
||||
dependsOn(rootProject.installDist)
|
||||
setSystemProperties(test.getSystemProperties())
|
||||
systemProperty 'acctests.runBesuAsProcess', 'true'
|
||||
// Set to any time > 60 minutes to run the soak test for longer
|
||||
// systemProperty 'acctests.soakTimeMins', '120'
|
||||
systemProperty 'java.security.properties', "${buildDir}/resources/test/acceptanceTesting.security"
|
||||
mustRunAfter rootProject.subprojects*.test
|
||||
description = 'Runs BFT soak test.'
|
||||
group = 'verification'
|
||||
|
||||
jvmArgs "-XX:ErrorFile=${buildDir}/jvmErrorLogs/java_err_pid%p.log"
|
||||
|
||||
testLogging {
|
||||
exceptionFormat = 'full'
|
||||
showStackTraces = true
|
||||
showStandardStreams = true
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
}
|
||||
|
||||
doFirst { mkdir "${buildDir}/jvmErrorLogs" }
|
||||
}
|
||||
|
||||
task acceptanceTestPrivacy(type: Test) {
|
||||
inputs.property "integration.date", LocalTime.now() // so it runs at every invocation
|
||||
include '**/privacy/**'
|
||||
|
||||
@@ -19,7 +19,7 @@ import "./EventEmitter.sol";
|
||||
// compile with:
|
||||
// solc CrossContractReader.sol --bin --abi --optimize --overwrite -o .
|
||||
// then create web3j wrappers with:
|
||||
// web3j solidity generate -b ./generated/CrossContractReader.bin -a ./generated/CrossContractReader.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
// web3j generate solidity -b ./generated/CrossContractReader.bin -a ./generated/CrossContractReader.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
contract CrossContractReader {
|
||||
uint counter;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ pragma solidity >=0.7.0 <0.9.0;
|
||||
// compile with:
|
||||
// solc EventEmitter.sol --bin --abi --optimize --overwrite -o .
|
||||
// then create web3j wrappers with:
|
||||
// web3j solidity generate -b ./generated/EventEmitter.bin -a ./generated/EventEmitter.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
// web3j generate solidity -b ./generated/EventEmitter.bin -a ./generated/EventEmitter.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
contract EventEmitter {
|
||||
address owner;
|
||||
event stored(address _to, uint _amount);
|
||||
|
||||
@@ -19,7 +19,7 @@ import "./SimpleStorage.sol";
|
||||
// compile with:
|
||||
// solc RemoteSimpleStorage.sol --bin --abi --optimize --overwrite -o .
|
||||
// then create web3j wrappers with:
|
||||
// web3j solidity generate -b ./generated/RemoteSimpleStorage.bin -a ./generated/RemoteSimpleStorage.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
// web3j generate solidity -b ./generated/RemoteSimpleStorage.bin -a ./generated/RemoteSimpleStorage.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
contract RemoteSimpleStorage {
|
||||
SimpleStorage public simpleStorage;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ pragma solidity >=0.7.0 <0.9.0;
|
||||
// compile with:
|
||||
// solc RevertReason.sol --bin --abi --optimize --overwrite -o .
|
||||
// then create web3j wrappers with:
|
||||
// web3j solidity generate -b ./generated/RevertReason.bin -a ./generated/RevertReason.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
// web3j generate solidity -b ./generated/RevertReason.bin -a ./generated/RevertReason.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
contract RevertReason {
|
||||
|
||||
function revertWithRevertReason() public pure returns (bool) {
|
||||
|
||||
@@ -17,7 +17,7 @@ pragma solidity >=0.7.0 <0.8.20;
|
||||
// compile with:
|
||||
// solc SimpleStorage.sol --bin --abi --optimize --overwrite -o .
|
||||
// then create web3j wrappers with:
|
||||
// web3j solidity generate -b ./generated/SimpleStorage.bin -a ./generated/SimpleStorage.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
// web3j generate solidity -b ./generated/SimpleStorage.bin -a ./generated/SimpleStorage.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
contract SimpleStorage {
|
||||
uint data;
|
||||
|
||||
|
||||
21
acceptance-tests/tests/shanghai/build.gradle
Normal file
21
acceptance-tests/tests/shanghai/build.gradle
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
plugins {
|
||||
id 'org.web3j' version '4.11.3'
|
||||
id 'org.web3j.solidity' version '0.4.1'
|
||||
}
|
||||
|
||||
jar { enabled = true }
|
||||
|
||||
web3j {
|
||||
generatedPackageName = 'org.hyperledger.besu.tests.web3j.generated'
|
||||
}
|
||||
|
||||
sourceSets.main.solidity.srcDirs = [
|
||||
"$projectDir/shanghaicontracts"
|
||||
]
|
||||
|
||||
solidity {
|
||||
resolvePackages = false
|
||||
version '0.8.25'
|
||||
evmVersion 'shanghai'
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
pragma solidity >=0.8.20;
|
||||
|
||||
// compile with:
|
||||
// solc SimpleStorageShanghai.sol --bin --abi --optimize --overwrite -o .
|
||||
// then create web3j wrappers with:
|
||||
// web3j generate solidity -b ./SimpleStorageShanghai.bin -a ./SimpleStorageShanghai.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated
|
||||
contract SimpleStorageShanghai {
|
||||
uint data;
|
||||
|
||||
function set(uint value) public {
|
||||
data = value;
|
||||
}
|
||||
|
||||
function get() public view returns (uint) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,14 @@ public class BftAcceptanceTestParameterization {
|
||||
@FunctionalInterface
|
||||
public interface NodeCreator {
|
||||
|
||||
BesuNode create(BesuNodeFactory factory, String name) throws Exception;
|
||||
BesuNode create(BesuNodeFactory factory, String name, boolean fixedPort) throws Exception;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FixedPortNodeCreator {
|
||||
|
||||
BesuNode createFixedPort(BesuNodeFactory factory, String name, boolean fixedPort)
|
||||
throws Exception;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
@@ -57,7 +64,11 @@ public class BftAcceptanceTestParameterization {
|
||||
}
|
||||
|
||||
public BesuNode createNode(BesuNodeFactory factory, String name) throws Exception {
|
||||
return creatorFn.create(factory, name);
|
||||
return creatorFn.create(factory, name, false);
|
||||
}
|
||||
|
||||
public BesuNode createNodeFixedPort(BesuNodeFactory factory, String name) throws Exception {
|
||||
return creatorFn.create(factory, name, true);
|
||||
}
|
||||
|
||||
public BesuNode createNodeWithValidators(
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* 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.tests.acceptance.bftsoak;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.hyperledger.besu.config.JsonUtil;
|
||||
import org.hyperledger.besu.tests.acceptance.bft.BftAcceptanceTestParameterization;
|
||||
import org.hyperledger.besu.tests.acceptance.bft.ParameterizedBftTestBase;
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
|
||||
import org.hyperledger.besu.tests.web3j.generated.SimpleStorage;
|
||||
import org.hyperledger.besu.tests.web3j.generated.SimpleStorageShanghai;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
public class BftMiningSoakTest extends ParameterizedBftTestBase {
|
||||
|
||||
private final int NUM_STEPS = 5;
|
||||
|
||||
private final int MIN_TEST_TIME_MINS = 60;
|
||||
|
||||
private static final long ONE_MINUTE = Duration.of(1, ChronoUnit.MINUTES).toMillis();
|
||||
|
||||
private static final long THREE_MINUTES = Duration.of(1, ChronoUnit.MINUTES).toMillis();
|
||||
|
||||
private static final long TEN_SECONDS = Duration.of(10, ChronoUnit.SECONDS).toMillis();
|
||||
|
||||
static int getTestDurationMins() {
|
||||
// Use a default soak time of 60 mins
|
||||
return Integer.getInteger("acctests.soakTimeMins", 60);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@MethodSource("factoryFunctions")
|
||||
public void shouldBeStableDuringLongTest(
|
||||
final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception {
|
||||
setUp(testName, nodeFactory);
|
||||
|
||||
// There is a minimum amount of time the test can be run for, due to hard coded delays
|
||||
// in between certain steps. There should be no upper-limit to how long the test is run for
|
||||
assertThat(getTestDurationMins()).isGreaterThanOrEqualTo(MIN_TEST_TIME_MINS);
|
||||
|
||||
final BesuNode minerNode1 = nodeFactory.createNodeFixedPort(besu, "miner1");
|
||||
final BesuNode minerNode2 = nodeFactory.createNodeFixedPort(besu, "miner2");
|
||||
final BesuNode minerNode3 = nodeFactory.createNodeFixedPort(besu, "miner3");
|
||||
final BesuNode minerNode4 = nodeFactory.createNodeFixedPort(besu, "miner4");
|
||||
|
||||
// Each step should be given a minimum of 3 minutes to complete successfully. If the time
|
||||
// give to run the soak test results in a time-per-step lower than this then the time
|
||||
// needs to be increased.
|
||||
assertThat(getTestDurationMins() / NUM_STEPS).isGreaterThanOrEqualTo(3);
|
||||
|
||||
cluster.start(minerNode1, minerNode2, minerNode3, minerNode4);
|
||||
|
||||
cluster.verify(blockchain.reachesHeight(minerNode1, 1, 85));
|
||||
|
||||
// Setup
|
||||
// Deploy a contract that we'll invoke periodically to ensure state
|
||||
// is correct during the test, especially after stopping nodes and
|
||||
// applying new forks.
|
||||
SimpleStorage simpleStorageContract =
|
||||
minerNode1.execute(contractTransactions.createSmartContract(SimpleStorage.class));
|
||||
|
||||
// Check the contract address is as expected for this sender & nonce
|
||||
contractVerifier
|
||||
.validTransactionReceipt("0x42699a7612a82f1d9c36148af9c77354759b210b")
|
||||
.verify(simpleStorageContract);
|
||||
|
||||
// Before upgrading to newer forks, try creating a shanghai-evm contract and check that
|
||||
// the transaction fails
|
||||
try {
|
||||
minerNode1.execute(contractTransactions.createSmartContract(SimpleStorageShanghai.class));
|
||||
Assertions.fail("Shanghai transaction should not be executed on a pre-shanghai chain");
|
||||
} catch (RuntimeException e) {
|
||||
assertThat(e.getMessage())
|
||||
.contains(
|
||||
"Revert reason: 'Transaction processing could not be completed due to an exception'");
|
||||
}
|
||||
|
||||
// Should initially be set to 0
|
||||
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(0));
|
||||
|
||||
// Set to something new
|
||||
simpleStorageContract.set(BigInteger.valueOf(101)).send();
|
||||
|
||||
// Check the state of the contract has updated correctly. We'll set & get this several times
|
||||
// during the test
|
||||
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(101));
|
||||
|
||||
// Step 1
|
||||
// Run for the configured time period, periodically checking that
|
||||
// the chain is progressing as expected
|
||||
BigInteger chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
assertThat(chainHeight.compareTo(BigInteger.ZERO)).isGreaterThanOrEqualTo(1);
|
||||
BigInteger lastChainHeight = chainHeight;
|
||||
|
||||
Instant startTime = Instant.now();
|
||||
Instant nextStepEndTime = startTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES);
|
||||
|
||||
while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) {
|
||||
Thread.sleep(ONE_MINUTE);
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
|
||||
// With 1-second block period chain height should have moved on by at least 50 blocks
|
||||
assertThat(chainHeight.compareTo(lastChainHeight.add(BigInteger.valueOf(50))))
|
||||
.isGreaterThanOrEqualTo(1);
|
||||
lastChainHeight = chainHeight;
|
||||
}
|
||||
Instant previousStepEndTime = Instant.now();
|
||||
|
||||
// Step 2
|
||||
// Stop one of the nodes, check that the chain continues mining
|
||||
// blocks
|
||||
stopNode(minerNode4);
|
||||
|
||||
nextStepEndTime =
|
||||
previousStepEndTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES);
|
||||
|
||||
while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) {
|
||||
Thread.sleep(ONE_MINUTE);
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
|
||||
// Chain height should have moved on by at least 5 blocks
|
||||
assertThat(chainHeight.compareTo(lastChainHeight.add(BigInteger.valueOf(20))))
|
||||
.isGreaterThanOrEqualTo(1);
|
||||
lastChainHeight = chainHeight;
|
||||
}
|
||||
previousStepEndTime = Instant.now();
|
||||
|
||||
// Step 3
|
||||
// Stop another one of the nodes, check that the chain now stops
|
||||
// mining blocks
|
||||
stopNode(minerNode3);
|
||||
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
lastChainHeight = chainHeight;
|
||||
|
||||
// Leave the chain stalled for 3 minutes. Check no new blocks are mined. Then
|
||||
// resume the other validators.
|
||||
nextStepEndTime = previousStepEndTime.plus(3, ChronoUnit.MINUTES);
|
||||
while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) {
|
||||
Thread.sleep(ONE_MINUTE);
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
|
||||
// Chain height should not have moved on
|
||||
assertThat(chainHeight.equals(lastChainHeight)).isTrue();
|
||||
}
|
||||
|
||||
// Step 4
|
||||
// Restart both of the stopped nodes. Check that the chain resumes
|
||||
// mining blocks
|
||||
startNode(minerNode3);
|
||||
|
||||
startNode(minerNode4);
|
||||
|
||||
previousStepEndTime = Instant.now();
|
||||
|
||||
// This step gives the stalled chain time to re-sync and agree on the next BFT round
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
nextStepEndTime =
|
||||
previousStepEndTime.plus((getTestDurationMins() / NUM_STEPS), ChronoUnit.MINUTES);
|
||||
lastChainHeight = chainHeight;
|
||||
|
||||
while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) {
|
||||
Thread.sleep(ONE_MINUTE);
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
lastChainHeight = chainHeight;
|
||||
}
|
||||
previousStepEndTime = Instant.now();
|
||||
|
||||
// By this loop it should be producing blocks again
|
||||
nextStepEndTime =
|
||||
previousStepEndTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES);
|
||||
|
||||
while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) {
|
||||
Thread.sleep(ONE_MINUTE);
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
|
||||
// Chain height should have moved on by at least 1 block
|
||||
assertThat(chainHeight.compareTo(lastChainHeight.add(BigInteger.valueOf(1))))
|
||||
.isGreaterThanOrEqualTo(1);
|
||||
lastChainHeight = chainHeight;
|
||||
}
|
||||
|
||||
// Update our smart contract before upgrading from berlin to london
|
||||
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(101));
|
||||
simpleStorageContract.set(BigInteger.valueOf(201)).send();
|
||||
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(201));
|
||||
|
||||
// Upgrade the chain from berlin to london in 120 blocks time
|
||||
upgradeToLondon(
|
||||
minerNode1, minerNode2, minerNode3, minerNode4, lastChainHeight.intValue() + 120);
|
||||
|
||||
previousStepEndTime = Instant.now();
|
||||
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
nextStepEndTime =
|
||||
previousStepEndTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES);
|
||||
lastChainHeight = chainHeight;
|
||||
|
||||
while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) {
|
||||
Thread.sleep(ONE_MINUTE);
|
||||
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
|
||||
|
||||
// Chain height should have moved on by at least 50 blocks
|
||||
assertThat(chainHeight.compareTo(lastChainHeight.add(BigInteger.valueOf(50))))
|
||||
.isGreaterThanOrEqualTo(1);
|
||||
lastChainHeight = chainHeight;
|
||||
}
|
||||
|
||||
// Check that the state of our smart contract is still correct
|
||||
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(201));
|
||||
|
||||
// Update it once more to check new transactions are mined OK
|
||||
simpleStorageContract.set(BigInteger.valueOf(301)).send();
|
||||
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(301));
|
||||
|
||||
// Upgrade the chain to shanghai in 120 seconds. Then try to deploy a shanghai contract
|
||||
upgradeToShanghai(
|
||||
minerNode1, minerNode2, minerNode3, minerNode4, Instant.now().getEpochSecond() + 120);
|
||||
|
||||
Thread.sleep(THREE_MINUTES);
|
||||
|
||||
SimpleStorageShanghai simpleStorageContractShanghai =
|
||||
minerNode1.execute(contractTransactions.createSmartContract(SimpleStorageShanghai.class));
|
||||
|
||||
// Check the contract address is as expected for this sender & nonce
|
||||
contractVerifier
|
||||
.validTransactionReceipt("0x05d91b9031a655d08e654177336d08543ac4b711")
|
||||
.verify(simpleStorageContractShanghai);
|
||||
}
|
||||
|
||||
private static void updateGenesisConfigToLondon(
|
||||
final BesuNode minerNode, final boolean zeroBaseFeeEnabled, final int blockNumber) {
|
||||
|
||||
if (minerNode.getGenesisConfig().isPresent()) {
|
||||
final ObjectNode genesisConfigNode =
|
||||
JsonUtil.objectNodeFromString(minerNode.getGenesisConfig().get());
|
||||
final ObjectNode config = (ObjectNode) genesisConfigNode.get("config");
|
||||
config.put("londonBlock", blockNumber);
|
||||
config.put("zeroBaseFee", zeroBaseFeeEnabled);
|
||||
minerNode.setGenesisConfig(genesisConfigNode.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateGenesisConfigToShanghai(
|
||||
final BesuNode minerNode, final long blockTimestamp) {
|
||||
|
||||
if (minerNode.getGenesisConfig().isPresent()) {
|
||||
final ObjectNode genesisConfigNode =
|
||||
JsonUtil.objectNodeFromString(minerNode.getGenesisConfig().get());
|
||||
final ObjectNode config = (ObjectNode) genesisConfigNode.get("config");
|
||||
config.put("shanghaiTime", blockTimestamp);
|
||||
minerNode.setGenesisConfig(genesisConfigNode.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeToLondon(
|
||||
final BesuNode minerNode1,
|
||||
final BesuNode minerNode2,
|
||||
final BesuNode minerNode3,
|
||||
final BesuNode minerNode4,
|
||||
final int londonBlockNumber)
|
||||
throws InterruptedException {
|
||||
// Node 1
|
||||
stopNode(minerNode1);
|
||||
updateGenesisConfigToLondon(minerNode1, true, londonBlockNumber);
|
||||
startNode(minerNode1);
|
||||
|
||||
// Node 2
|
||||
stopNode(minerNode2);
|
||||
updateGenesisConfigToLondon(minerNode2, true, londonBlockNumber);
|
||||
startNode(minerNode2);
|
||||
|
||||
// Node 3
|
||||
stopNode(minerNode3);
|
||||
updateGenesisConfigToLondon(minerNode3, true, londonBlockNumber);
|
||||
startNode(minerNode3);
|
||||
|
||||
// Node 4
|
||||
stopNode(minerNode4);
|
||||
updateGenesisConfigToLondon(minerNode4, true, londonBlockNumber);
|
||||
startNode(minerNode4);
|
||||
}
|
||||
|
||||
private void upgradeToShanghai(
|
||||
final BesuNode minerNode1,
|
||||
final BesuNode minerNode2,
|
||||
final BesuNode minerNode3,
|
||||
final BesuNode minerNode4,
|
||||
final long shanghaiTime)
|
||||
throws InterruptedException {
|
||||
// Node 1
|
||||
stopNode(minerNode1);
|
||||
updateGenesisConfigToShanghai(minerNode1, shanghaiTime);
|
||||
startNode(minerNode1);
|
||||
|
||||
// Node 2
|
||||
stopNode(minerNode2);
|
||||
updateGenesisConfigToShanghai(minerNode2, shanghaiTime);
|
||||
startNode(minerNode2);
|
||||
|
||||
// Node 3
|
||||
stopNode(minerNode3);
|
||||
updateGenesisConfigToShanghai(minerNode3, shanghaiTime);
|
||||
startNode(minerNode3);
|
||||
|
||||
// Node 4
|
||||
stopNode(minerNode4);
|
||||
updateGenesisConfigToShanghai(minerNode4, shanghaiTime);
|
||||
startNode(minerNode4);
|
||||
}
|
||||
|
||||
// Start a node with a delay before returning to give it time to start
|
||||
private void startNode(final BesuNode node) throws InterruptedException {
|
||||
cluster.startNode(node);
|
||||
Thread.sleep(TEN_SECONDS);
|
||||
}
|
||||
|
||||
// Stop a node with a delay before returning to give it time to stop
|
||||
private void stopNode(final BesuNode node) throws InterruptedException {
|
||||
cluster.stopNode(node);
|
||||
Thread.sleep(TEN_SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDownAcceptanceTestBase() {
|
||||
cluster.stop();
|
||||
super.tearDownAcceptanceTestBase();
|
||||
}
|
||||
}
|
||||
@@ -386,7 +386,7 @@ task checkMavenCoordinateCollisions {
|
||||
getAllprojects().forEach {
|
||||
if (it.properties.containsKey('publishing') && it.jar?.enabled) {
|
||||
def coordinate = it.publishing?.publications[0].coordinates
|
||||
if (coordinates.containsKey(coordinate)) {
|
||||
if (coordinate.toString().startsWith("org") && coordinates.containsKey(coordinate)) {
|
||||
throw new GradleException("Duplicate maven coordinates detected, ${coordinate} is used by " +
|
||||
"both ${coordinates[coordinate]} and ${it.path}.\n" +
|
||||
"Please add a `publishing` script block to one or both subprojects.")
|
||||
|
||||
@@ -28,6 +28,7 @@ rootProject.name='besu'
|
||||
include 'acceptance-tests:test-plugins'
|
||||
include 'acceptance-tests:dsl'
|
||||
include 'acceptance-tests:tests'
|
||||
include 'acceptance-tests:tests:shanghai'
|
||||
include 'besu'
|
||||
include 'config'
|
||||
include 'consensus:clique'
|
||||
|
||||
Reference in New Issue
Block a user