mirror of https://github.com/hyperledger/besu
"container-tests" module. (#1894)
Signed-off-by: Mark Terry <mark.terry@consensys.net>pull/2149/head
parent
a95a008d1d
commit
6fe29e5dd9
@ -0,0 +1,17 @@ |
||||
|
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* 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 |
||||
*/ |
||||
|
||||
jar { enabled = false } |
@ -0,0 +1,38 @@ |
||||
|
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* 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 |
||||
*/ |
||||
|
||||
jar { enabled = false } |
||||
|
||||
dependencies { |
||||
testImplementation 'junit:junit' |
||||
testImplementation 'org.apache.logging.log4j:log4j-slf4j-impl' |
||||
testImplementation 'org.assertj:assertj-core' |
||||
testImplementation 'org.awaitility:awaitility' |
||||
testImplementation 'org.testcontainers:testcontainers' |
||||
testImplementation 'org.web3j:core' |
||||
testImplementation 'org.web3j:quorum' |
||||
} |
||||
|
||||
test.enabled = false |
||||
|
||||
task containerTests(type: Test) { |
||||
description = 'Runs GoQuorum <> Besu container tests.' |
||||
dependsOn(rootProject.distDocker) |
||||
def dockerBuildVersion = project.hasProperty('release.releaseVersion') ? project.property('release.releaseVersion') : "${rootProject.version}" |
||||
def imageName = "hyperledger/besu" |
||||
def image = "${imageName}:${dockerBuildVersion}" |
||||
systemProperty 'containertest.imagename', image |
||||
} |
@ -0,0 +1,297 @@ |
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* 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.container; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.function.Consumer; |
||||
|
||||
import com.github.dockerjava.api.command.CreateContainerCmd; |
||||
import org.awaitility.Awaitility; |
||||
import org.awaitility.core.ThrowingRunnable; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Rule; |
||||
import org.testcontainers.containers.BindMode; |
||||
import org.testcontainers.containers.GenericContainer; |
||||
import org.testcontainers.containers.Network; |
||||
import org.testcontainers.containers.wait.strategy.Wait; |
||||
import org.web3j.crypto.CipherException; |
||||
import org.web3j.crypto.Credentials; |
||||
import org.web3j.protocol.Web3j; |
||||
import org.web3j.protocol.core.methods.response.EthBlockNumber; |
||||
import org.web3j.protocol.core.methods.response.Web3ClientVersion; |
||||
import org.web3j.protocol.http.HttpService; |
||||
import org.web3j.quorum.Quorum; |
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"}) |
||||
public class ContainerTestBase { |
||||
private final String besuImage = System.getProperty("containertest.imagename"); |
||||
private final String goQuorumVersion = "21.1.0"; |
||||
private final String tesseraVersion = "21.1.0"; |
||||
|
||||
protected final String goQuorumTesseraPubKey = "3XGBIf+x8IdVQOVfIsbRnHwTYOJP/Fx84G8gMmy8qDM="; |
||||
protected final String besuTesseraPubKey = "8JJLEAbq6o9m4Kqm++v0Y1n9Z2ryAFtZTyhnxSKWgws="; |
||||
|
||||
private final Network containerNetwork = Network.SHARED; |
||||
protected Quorum besuWeb3j; |
||||
protected Quorum goQuorumWeb3j; |
||||
|
||||
// General config
|
||||
private final String hostGenesisPath = "/genesis.json"; |
||||
private final String hostKeyPath = "/keys/"; |
||||
private final String containerKeyPath = "/tmp/keys/"; |
||||
private final Integer networkId = 2020; |
||||
|
||||
// Besu config
|
||||
private final Integer besuP2pPort = 30303; |
||||
private final Integer besuRpcPort = 8545; |
||||
private final String besuContainerGenesisPath = "/opt/besu/genesis.json"; |
||||
private final String besuNetworkAlias = "besuNode"; |
||||
private final String hostBesuKeyPath = "/besu/data/key"; |
||||
private final String containerBesuKeyPath = "/opt/besu/key"; |
||||
|
||||
// GoQuorum + Tessera shared config
|
||||
private final String ipcBindDir = "/tmp/ipc/"; |
||||
private final String ipcDirPath = "/ipc/"; |
||||
private final String ipcFilePath = "test.ipc"; |
||||
private final String containerIpcPath = ipcBindDir + ipcFilePath; |
||||
|
||||
// Tessera config
|
||||
private final String tesseraContainerPrivKey1Path = "/tmp/keys/key1.key"; |
||||
private final String tesseraContainerPubKey1Path = "/tmp/keys/key1.pub"; |
||||
private final String tesseraContainerPrivKey2Path = "/tmp/keys/key2.key"; |
||||
private final String tesseraContainerPubKey2Path = "/tmp/keys/key2.pub"; |
||||
private final String tesseraContainerConfigFilePath = "/tmp/tessera/tesseraConfig.json"; |
||||
protected final int tesseraRestPort = 9081; |
||||
private final int tesseraQ2TRestPort = 9003; |
||||
private final String hostTesseraResources = "/tessera/"; |
||||
private final String containerTesseraResources = "/tmp/tessera/"; |
||||
private final int tesseraP2pPort = 9001; |
||||
|
||||
// GoQuorum config
|
||||
private final String goQuorumContainerDatadir = "/tmp/qdata/"; |
||||
private final String goQuorumContainerGenesis = "/tmp/qdata/genesis.json"; |
||||
private final String goQuorumContainerGethIpcPath = goQuorumContainerDatadir + "/geth.ipc"; |
||||
private final Integer goQuorumP2pPort = 30303; |
||||
private final Integer goQuorumRpcPort = 8545; |
||||
|
||||
@Rule public final GenericContainer besuContainer = buildBesuContainer(); |
||||
|
||||
@Rule |
||||
public final GenericContainer tesseraGoQuorumContainer = |
||||
buildGoQuorumTesseraContainer( |
||||
ipcDirPath, |
||||
ipcBindDir, |
||||
containerIpcPath, |
||||
tesseraContainerPrivKey1Path, |
||||
tesseraContainerPubKey1Path); |
||||
|
||||
@Rule |
||||
public final GenericContainer tesseraBesuContainer = |
||||
buildBesuTesseraContainer(tesseraContainerPrivKey2Path, tesseraContainerPubKey2Path); |
||||
|
||||
@Rule |
||||
public final GenericContainer goQuorumContainer = |
||||
buildGoQuorumContainer(ipcDirPath, ipcBindDir, containerIpcPath);; |
||||
|
||||
@Before |
||||
public void setUp() throws IOException, InterruptedException { |
||||
besuWeb3j = |
||||
buildWeb3JQuorum( |
||||
besuContainer.getContainerIpAddress(), besuContainer.getMappedPort(besuRpcPort)); |
||||
goQuorumWeb3j = |
||||
buildWeb3JQuorum( |
||||
goQuorumContainer.getContainerIpAddress(), |
||||
goQuorumContainer.getMappedPort(goQuorumRpcPort)); |
||||
|
||||
waitFor(10, () -> assertClientVersion(besuWeb3j, "dev")); |
||||
waitFor(10, () -> assertClientVersion(goQuorumWeb3j, goQuorumVersion)); |
||||
|
||||
// Tell GoQuorum to peer to Besu
|
||||
goQuorumContainer.execInContainer( |
||||
"geth", |
||||
"--exec", |
||||
"admin.addPeer(\"enode://11b72d1e2fdde254a493047d4061f3b62cc2ba59f4c1b0cf41dda4ba0d77f0bfe4f932ccf9f60203b1d47753f69edf1c80e755e3159e596b1f6aa03cb0c275c4@" |
||||
+ besuNetworkAlias |
||||
+ ":" |
||||
+ besuP2pPort |
||||
+ "\")", |
||||
"attach", |
||||
goQuorumContainerGethIpcPath); |
||||
|
||||
waitFor(30, () -> assertBlockHeight(besuWeb3j, 5)); |
||||
waitFor(30, () -> assertBlockHeight(goQuorumWeb3j, 5)); |
||||
} |
||||
|
||||
@After |
||||
public void tearDown() throws IOException { |
||||
boolean fileExists = Files.deleteIfExists(Path.of(getResourcePath(ipcDirPath + ipcFilePath))); |
||||
if (!fileExists) { |
||||
System.out.println("Unable to delete tx IPC file."); |
||||
} |
||||
} |
||||
|
||||
private GenericContainer buildBesuContainer() { |
||||
return new GenericContainer(besuImage) |
||||
.withNetwork(containerNetwork) |
||||
.withNetworkAliases(besuNetworkAlias) |
||||
.withExposedPorts(besuRpcPort, besuP2pPort) |
||||
.withClasspathResourceMapping(hostBesuKeyPath, containerBesuKeyPath, BindMode.READ_ONLY) |
||||
.withClasspathResourceMapping(hostGenesisPath, besuContainerGenesisPath, BindMode.READ_ONLY) |
||||
.withClasspathResourceMapping(hostKeyPath, containerKeyPath, BindMode.READ_ONLY) |
||||
.withCommand( |
||||
"--genesis-file", |
||||
besuContainerGenesisPath, |
||||
"--network-id", |
||||
networkId.toString(), |
||||
"--p2p-port", |
||||
besuP2pPort.toString(), |
||||
"--rpc-http-enabled", |
||||
"--rpc-http-port", |
||||
besuRpcPort.toString(), |
||||
"--rpc-http-api", |
||||
"ADMIN,ETH,NET,WEB3,GOQUORUM", |
||||
"--goquorum-compatibility-enabled", |
||||
"--min-gas-price", |
||||
"0", |
||||
"--privacy-public-key-file", |
||||
"/tmp/keys/key2.pub", |
||||
"--privacy-url", |
||||
"http://" + "besuTessera" + ':' + tesseraQ2TRestPort); |
||||
} |
||||
|
||||
private GenericContainer buildGoQuorumTesseraContainer( |
||||
final String ipcPath, |
||||
final String ipcBindDir, |
||||
final String containerIpcPath, |
||||
final String privKeyPath, |
||||
final String pubKeyPath) { |
||||
return new GenericContainer("quorumengineering/tessera:" + tesseraVersion) |
||||
.withNetwork(containerNetwork) |
||||
.withNetworkAliases("goQuorumTessera") |
||||
.withClasspathResourceMapping( |
||||
hostTesseraResources, containerTesseraResources, BindMode.READ_ONLY) |
||||
.withClasspathResourceMapping(ipcPath, ipcBindDir, BindMode.READ_WRITE) |
||||
.withClasspathResourceMapping(hostKeyPath, containerKeyPath, BindMode.READ_ONLY) |
||||
.withCommand( |
||||
"--configfile " + tesseraContainerConfigFilePath, |
||||
"-o serverConfigs[0].serverAddress=http://localhost:" + tesseraRestPort, |
||||
"-o serverConfigs[1].serverAddress=unix:" + containerIpcPath, |
||||
"-o serverConfigs[2].serverAddress=http://" + "goQuorumTessera" + ":" + tesseraP2pPort, |
||||
"-o keys.keyData[0].privateKeyPath=" + privKeyPath, |
||||
"-o keys.keyData[0].publicKeyPath=" + pubKeyPath, |
||||
"-o peer[0].url=http://" + "goQuorumTessera" + ":" + tesseraP2pPort + "/", |
||||
"-o peer[1].url=http://" + "besuTessera" + ":" + tesseraP2pPort + "/") |
||||
.withExposedPorts(tesseraP2pPort, tesseraRestPort) |
||||
.waitingFor(Wait.forHttp("/upcheck")); |
||||
} |
||||
|
||||
private GenericContainer buildBesuTesseraContainer( |
||||
final String privKeyPath, final String pubKeyPath) { |
||||
return new GenericContainer("quorumengineering/tessera:" + tesseraVersion) |
||||
.withNetwork(containerNetwork) |
||||
.withNetworkAliases("besuTessera") |
||||
.withClasspathResourceMapping( |
||||
hostTesseraResources, containerTesseraResources, BindMode.READ_ONLY) |
||||
.withClasspathResourceMapping(hostKeyPath, containerKeyPath, BindMode.READ_ONLY) |
||||
.withCommand( |
||||
"--configfile " + tesseraContainerConfigFilePath, |
||||
"-o serverConfigs[0].serverAddress=http://localhost:" + tesseraRestPort, |
||||
"-o serverConfigs[1].serverAddress=http://localhost:" + tesseraQ2TRestPort, |
||||
"-o serverConfigs[2].serverAddress=http://" + "besuTessera" + ":" + tesseraP2pPort, |
||||
"-o keys.keyData[0].privateKeyPath=" + privKeyPath, |
||||
"-o keys.keyData[0].publicKeyPath=" + pubKeyPath, |
||||
"-o peer[0].url=http://" + "besuTessera" + ":" + tesseraP2pPort + "/", |
||||
"-o peer[1].url=http://" + "goQuorumTessera" + ":" + tesseraP2pPort + "/") |
||||
.withExposedPorts(tesseraP2pPort, tesseraRestPort) |
||||
.waitingFor(Wait.forHttp("/upcheck")); |
||||
} |
||||
|
||||
private GenericContainer buildGoQuorumContainer( |
||||
final String ipcPath, final String ipcBindDir, final String containerIpcPath) { |
||||
return new GenericContainer("quorumengineering/quorum:" + goQuorumVersion) |
||||
.withNetwork(containerNetwork) |
||||
.dependsOn(tesseraGoQuorumContainer) |
||||
.withExposedPorts(goQuorumRpcPort, goQuorumP2pPort) |
||||
.withClasspathResourceMapping( |
||||
"/goQuorum/qdata/", goQuorumContainerDatadir, BindMode.READ_ONLY) |
||||
.withClasspathResourceMapping(hostGenesisPath, goQuorumContainerGenesis, BindMode.READ_ONLY) |
||||
.withClasspathResourceMapping(ipcPath, ipcBindDir, BindMode.READ_WRITE) |
||||
.withEnv("PRIVATE_CONFIG", containerIpcPath) |
||||
.withCreateContainerCmdModifier( |
||||
(Consumer<CreateContainerCmd>) |
||||
cmd -> |
||||
cmd.withEntrypoint( |
||||
"/bin/ash", |
||||
"-c", |
||||
"geth init " |
||||
+ goQuorumContainerGenesis |
||||
+ " --datadir " |
||||
+ goQuorumContainerDatadir |
||||
+ " && geth --datadir " |
||||
+ goQuorumContainerDatadir |
||||
+ " --networkid " |
||||
+ networkId.toString() |
||||
+ " --rpc" |
||||
+ " --rpcaddr 0.0.0.0" |
||||
+ " --rpcport " |
||||
+ goQuorumRpcPort.toString() |
||||
+ " --rpcapi admin,eth,debug,miner,net,shh,txpool,personal,web3,quorum,quorumExtension,clique" |
||||
+ " --emitcheckpoints" |
||||
+ " --port " |
||||
+ goQuorumP2pPort.toString() |
||||
+ " --verbosity" |
||||
+ " 3" |
||||
+ " --nousb" |
||||
+ " --nodekey " |
||||
+ goQuorumContainerDatadir |
||||
+ "/nodeKey")); |
||||
} |
||||
|
||||
private Quorum buildWeb3JQuorum(final String containerIpAddress, final Integer mappedPort) { |
||||
return Quorum.build(new HttpService("http://" + containerIpAddress + ":" + mappedPort)); |
||||
} |
||||
|
||||
private void assertClientVersion(final Web3j web3, final String clientString) throws IOException { |
||||
final Web3ClientVersion clientVersionResult = web3.web3ClientVersion().send(); |
||||
assertThat(clientVersionResult.getWeb3ClientVersion()).contains(clientString); |
||||
} |
||||
|
||||
private void assertBlockHeight(final Web3j web3j, final int blockHeight) throws IOException { |
||||
final EthBlockNumber blockNumberResult = web3j.ethBlockNumber().send(); |
||||
assertThat(blockNumberResult.getBlockNumber().intValueExact()) |
||||
.isGreaterThanOrEqualTo(blockHeight); |
||||
} |
||||
|
||||
public static void waitFor(final int timeout, final ThrowingRunnable condition) { |
||||
Awaitility.await() |
||||
.ignoreExceptions() |
||||
.atMost(timeout, TimeUnit.SECONDS) |
||||
.untilAsserted(condition); |
||||
} |
||||
|
||||
private String getResourcePath(final String resourceName) { |
||||
return getClass().getResource(resourceName).getPath(); |
||||
} |
||||
|
||||
protected Credentials loadCredentials() throws IOException, CipherException { |
||||
return Credentials.create("8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"); |
||||
} |
||||
} |
@ -0,0 +1,195 @@ |
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* 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.container; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
import static org.hyperledger.besu.tests.container.helpers.ContractOperations.deployContractAndReturnAddress; |
||||
import static org.hyperledger.besu.tests.container.helpers.ContractOperations.generateRandomLogValue; |
||||
import static org.hyperledger.besu.tests.container.helpers.ContractOperations.getTransactionLog; |
||||
import static org.hyperledger.besu.tests.container.helpers.ContractOperations.sendLogEventAndReturnTransactionHash; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import okhttp3.OkHttpClient; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.web3j.crypto.CipherException; |
||||
import org.web3j.crypto.Credentials; |
||||
import org.web3j.protocol.exceptions.TransactionException; |
||||
import org.web3j.quorum.enclave.Enclave; |
||||
import org.web3j.quorum.enclave.Tessera; |
||||
import org.web3j.quorum.enclave.protocol.EnclaveService; |
||||
import org.web3j.quorum.tx.QuorumTransactionManager; |
||||
import org.web3j.tx.response.PollingTransactionReceiptProcessor; |
||||
|
||||
public class ContainerTests extends ContainerTestBase { |
||||
|
||||
private Credentials credentials; |
||||
private Enclave besuEnclave; |
||||
private EnclaveService besuEnclaveService; |
||||
private Enclave goQuorumEnclave; |
||||
private EnclaveService goQuorumEnclaveService; |
||||
private PollingTransactionReceiptProcessor besuPollingTransactionReceiptProcessor; |
||||
private PollingTransactionReceiptProcessor goQuorumPollingTransactionReceiptProcessor; |
||||
|
||||
@Before |
||||
public void testSetUp() throws IOException, CipherException { |
||||
besuEnclaveService = |
||||
new EnclaveService( |
||||
"http://" + tesseraBesuContainer.getHost(), |
||||
tesseraBesuContainer.getMappedPort(tesseraRestPort), |
||||
new OkHttpClient()); |
||||
besuEnclave = new Tessera(besuEnclaveService, besuWeb3j); |
||||
besuPollingTransactionReceiptProcessor = |
||||
new PollingTransactionReceiptProcessor(besuWeb3j, 1000, 10); |
||||
goQuorumEnclaveService = |
||||
new EnclaveService( |
||||
"http://" + tesseraGoQuorumContainer.getHost(), |
||||
tesseraGoQuorumContainer.getMappedPort(tesseraRestPort), |
||||
new OkHttpClient()); |
||||
goQuorumEnclave = new Tessera(goQuorumEnclaveService, goQuorumWeb3j); |
||||
goQuorumPollingTransactionReceiptProcessor = |
||||
new PollingTransactionReceiptProcessor(goQuorumWeb3j, 1000, 10); |
||||
credentials = loadCredentials(); |
||||
} |
||||
|
||||
@Test |
||||
public void contractShouldBeDeployedToBothNodes() throws IOException, TransactionException { |
||||
// create a GoQuorum transaction manager
|
||||
final QuorumTransactionManager qtm = |
||||
new QuorumTransactionManager( |
||||
goQuorumWeb3j, |
||||
credentials, |
||||
goQuorumTesseraPubKey, |
||||
Arrays.asList(goQuorumTesseraPubKey, besuTesseraPubKey), |
||||
goQuorumEnclave); |
||||
|
||||
// Get the deployed contract address
|
||||
final String contractAddress = |
||||
deployContractAndReturnAddress( |
||||
goQuorumWeb3j, |
||||
credentials, |
||||
qtm, |
||||
besuPollingTransactionReceiptProcessor, |
||||
goQuorumPollingTransactionReceiptProcessor); |
||||
|
||||
// Generate a random value to insert into the log
|
||||
final String logValue = generateRandomLogValue(); |
||||
|
||||
// Send the transaction and get the transaction hash
|
||||
final String transactionHash = |
||||
sendLogEventAndReturnTransactionHash( |
||||
goQuorumWeb3j, |
||||
credentials, |
||||
contractAddress, |
||||
qtm, |
||||
besuPollingTransactionReceiptProcessor, |
||||
goQuorumPollingTransactionReceiptProcessor, |
||||
logValue); |
||||
|
||||
// Get the transaction logs
|
||||
final String goQuorumResult = getTransactionLog(goQuorumWeb3j, transactionHash); |
||||
final String besuResult = getTransactionLog(besuWeb3j, transactionHash); |
||||
|
||||
assertThat(besuResult).isEqualTo(logValue); |
||||
assertThat(goQuorumResult).isEqualTo(logValue); |
||||
} |
||||
|
||||
@Test |
||||
public void contractShouldBeDeployedOnlyToGoQuorumNode() |
||||
throws IOException, TransactionException { |
||||
// create a quorum transaction manager
|
||||
final QuorumTransactionManager qtm = |
||||
new QuorumTransactionManager( |
||||
goQuorumWeb3j, |
||||
credentials, |
||||
goQuorumTesseraPubKey, |
||||
List.of(goQuorumTesseraPubKey), |
||||
goQuorumEnclave); |
||||
|
||||
// Get the deployed contract address
|
||||
final String contractAddress = |
||||
deployContractAndReturnAddress( |
||||
goQuorumWeb3j, |
||||
credentials, |
||||
qtm, |
||||
goQuorumPollingTransactionReceiptProcessor, |
||||
besuPollingTransactionReceiptProcessor); |
||||
|
||||
// Generate a random value to insert into the log
|
||||
final String logValue = generateRandomLogValue(); |
||||
|
||||
// Send the transaction and get the transaction hash
|
||||
final String transactionHash = |
||||
sendLogEventAndReturnTransactionHash( |
||||
goQuorumWeb3j, |
||||
credentials, |
||||
contractAddress, |
||||
qtm, |
||||
goQuorumPollingTransactionReceiptProcessor, |
||||
besuPollingTransactionReceiptProcessor, |
||||
logValue); |
||||
|
||||
// Assert the GoQuorum node has received the log
|
||||
final String quorumResult = getTransactionLog(goQuorumWeb3j, transactionHash); |
||||
assertThat(quorumResult).isEqualTo(logValue); |
||||
|
||||
// Assert the Besu node has not received the log
|
||||
assertThatThrownBy(() -> getTransactionLog(besuWeb3j, transactionHash)) |
||||
.hasMessageContaining("No log found"); |
||||
} |
||||
|
||||
@Test |
||||
public void contractShouldBeDeployedOnlyToBesuNode() throws IOException, TransactionException { |
||||
// create a GoQuorum transaction manager
|
||||
final QuorumTransactionManager qtm = |
||||
new QuorumTransactionManager( |
||||
besuWeb3j, credentials, besuTesseraPubKey, List.of(besuTesseraPubKey), besuEnclave); |
||||
|
||||
// Get the deployed contract address
|
||||
final String contractAddress = |
||||
deployContractAndReturnAddress( |
||||
besuWeb3j, |
||||
credentials, |
||||
qtm, |
||||
besuPollingTransactionReceiptProcessor, |
||||
goQuorumPollingTransactionReceiptProcessor); |
||||
|
||||
// Generate a random value to insert into the log
|
||||
final String logValue = generateRandomLogValue(); |
||||
|
||||
// Send the transaction and get the transaction hash
|
||||
final String transactionHash = |
||||
sendLogEventAndReturnTransactionHash( |
||||
besuWeb3j, |
||||
credentials, |
||||
contractAddress, |
||||
qtm, |
||||
besuPollingTransactionReceiptProcessor, |
||||
goQuorumPollingTransactionReceiptProcessor, |
||||
logValue); |
||||
|
||||
// Assert the Besu node has received the log
|
||||
final String besuResult = getTransactionLog(besuWeb3j, transactionHash); |
||||
assertThat(besuResult).isEqualTo(logValue); |
||||
|
||||
// Assert the GoQuorum node has not received the log
|
||||
assertThatThrownBy(() -> getTransactionLog(goQuorumWeb3j, transactionHash)) |
||||
.hasMessageContaining("No log found"); |
||||
} |
||||
} |
@ -0,0 +1,162 @@ |
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* 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.container.helpers; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import java.io.IOException; |
||||
import java.math.BigInteger; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.web3j.abi.FunctionEncoder; |
||||
import org.web3j.crypto.Credentials; |
||||
import org.web3j.crypto.RawTransaction; |
||||
import org.web3j.protocol.core.DefaultBlockParameterName; |
||||
import org.web3j.protocol.core.methods.response.EthGetTransactionCount; |
||||
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt; |
||||
import org.web3j.protocol.core.methods.response.EthSendTransaction; |
||||
import org.web3j.protocol.core.methods.response.Log; |
||||
import org.web3j.protocol.core.methods.response.TransactionReceipt; |
||||
import org.web3j.protocol.exceptions.TransactionException; |
||||
import org.web3j.quorum.Quorum; |
||||
import org.web3j.quorum.tx.QuorumTransactionManager; |
||||
import org.web3j.tx.response.PollingTransactionReceiptProcessor; |
||||
|
||||
public class ContractOperations { |
||||
public static String deployContractAndReturnAddress( |
||||
final Quorum quorumWeb3j, |
||||
final Credentials credentials, |
||||
final QuorumTransactionManager qtm, |
||||
final PollingTransactionReceiptProcessor pollingTransactionReceiptProcessorReturn, |
||||
final PollingTransactionReceiptProcessor pollingTransactionReceiptProcessorIgnore) |
||||
throws IOException, TransactionException { |
||||
// Build smart contract transaction
|
||||
final String simpleStorageContractBytecode = |
||||
"6080604052348015600f57600080fd5b5060de8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060275760003560e01c8062f88abf14602c575b600080fd5b605560048036036020811015604057600080fd5b81019080803590602001909291905050506057565b005b3373ffffffffffffffffffffffffffffffffffffffff167f748aa07c80b05bd067e3688dbb79d9f9583cd018be6a589a7c364cacd770e0a2826040518082815260200191505060405180910390a25056fea26469706673582212207df0d3ad8bced04b7bd476cc81a6233c0b575966c29b4af96450313628ee623964736f6c63430007040033"; |
||||
final String encodedConstructor = FunctionEncoder.encodeConstructor(Collections.emptyList()); |
||||
final String binaryAndInitCode = simpleStorageContractBytecode + encodedConstructor; |
||||
|
||||
final RawTransaction contractTransaction = |
||||
RawTransaction.createTransaction( |
||||
BigInteger.valueOf(getNonce(quorumWeb3j, credentials)), |
||||
BigInteger.ZERO, |
||||
BigInteger.valueOf(4300000), |
||||
"", |
||||
BigInteger.ZERO, |
||||
binaryAndInitCode); |
||||
|
||||
// Send the signed transaction to quorum
|
||||
final EthSendTransaction sendContractTransactionResult = qtm.signAndSend(contractTransaction); |
||||
assertThat(sendContractTransactionResult.hasError()).isFalse(); |
||||
|
||||
pollNodeForTransactionReceipt( |
||||
pollingTransactionReceiptProcessorIgnore, |
||||
sendContractTransactionResult.getTransactionHash()); |
||||
|
||||
// Poll for the transaction receipt
|
||||
final TransactionReceipt transactionReceiptResult = |
||||
pollNodeForTransactionReceiptResult( |
||||
pollingTransactionReceiptProcessorReturn, |
||||
sendContractTransactionResult.getTransactionHash()); |
||||
|
||||
return transactionReceiptResult.getContractAddress(); |
||||
} |
||||
|
||||
public static String getTransactionLog(final Quorum web3j, final String transactionHash) |
||||
throws IOException { |
||||
final EthGetTransactionReceipt transactionReceiptResult = |
||||
web3j.ethGetTransactionReceipt(transactionHash).send(); |
||||
assertThat(transactionReceiptResult.getTransactionReceipt()) |
||||
.withFailMessage("Transaction for log not found.") |
||||
.isNotEmpty(); |
||||
final List<Log> logs = transactionReceiptResult.getTransactionReceipt().get().getLogs(); |
||||
assertThat(logs.size()).withFailMessage("No log found.").isEqualTo(1); |
||||
// Remove the 0x prefix
|
||||
return logs.get(0).getData().substring(2); |
||||
} |
||||
|
||||
public static String sendLogEventAndReturnTransactionHash( |
||||
final Quorum quorum, |
||||
final Credentials credentials, |
||||
final String contractAddress, |
||||
final QuorumTransactionManager qtm, |
||||
final PollingTransactionReceiptProcessor pollingTransactionReceiptProcessorReturn, |
||||
final PollingTransactionReceiptProcessor pollingTransactionReceiptProcessorIgnore, |
||||
final String param) |
||||
throws IOException, TransactionException { |
||||
final String functionSig = "0x00f88abf"; |
||||
final String data = functionSig + param; |
||||
final RawTransaction logEventTransaction = |
||||
RawTransaction.createTransaction( |
||||
BigInteger.valueOf(getNonce(quorum, credentials)), |
||||
BigInteger.ZERO, |
||||
BigInteger.valueOf(4300000), |
||||
contractAddress, |
||||
BigInteger.ZERO, |
||||
data); |
||||
|
||||
final EthSendTransaction sendTransactionResult = qtm.signAndSend(logEventTransaction); |
||||
assertThat(sendTransactionResult.hasError()).isFalse(); |
||||
|
||||
pollNodeForTransactionReceipt( |
||||
pollingTransactionReceiptProcessorIgnore, sendTransactionResult.getTransactionHash()); |
||||
|
||||
final TransactionReceipt logReceiptResult = |
||||
pollNodeForTransactionReceiptResult( |
||||
pollingTransactionReceiptProcessorReturn, sendTransactionResult.getTransactionHash()); |
||||
|
||||
return logReceiptResult.getTransactionHash(); |
||||
} |
||||
|
||||
public static String generateRandomLogValue() { |
||||
final StringBuilder randomValue = |
||||
new StringBuilder(Long.toHexString(((Double) (Math.random() * 100000000)).longValue())); |
||||
|
||||
while (randomValue.length() < 64) { |
||||
randomValue.insert(0, '0'); |
||||
} |
||||
return randomValue.toString(); |
||||
} |
||||
|
||||
public static int getNonce(final Quorum quorum, final Credentials credentials) |
||||
throws IOException { |
||||
final EthGetTransactionCount transactionCountResult = |
||||
quorum |
||||
.ethGetTransactionCount(credentials.getAddress(), DefaultBlockParameterName.LATEST) |
||||
.send(); |
||||
assertThat(transactionCountResult.hasError()).isFalse(); |
||||
return transactionCountResult.getTransactionCount().intValueExact(); |
||||
} |
||||
|
||||
public static TransactionReceipt pollNodeForTransactionReceiptResult( |
||||
final PollingTransactionReceiptProcessor pollingTransactionReceiptProcessor, |
||||
final String transactionHash) |
||||
throws IOException, TransactionException { |
||||
final TransactionReceipt transactionReceiptResult = |
||||
pollingTransactionReceiptProcessor.waitForTransactionReceipt(transactionHash); |
||||
assertThat(transactionReceiptResult.isStatusOK()).isTrue(); |
||||
return transactionReceiptResult; |
||||
} |
||||
|
||||
public static void pollNodeForTransactionReceipt( |
||||
final PollingTransactionReceiptProcessor pollingTransactionReceiptProcessor, |
||||
final String transactionHash) |
||||
throws IOException, TransactionException { |
||||
final TransactionReceipt transactionReceiptResult = |
||||
pollingTransactionReceiptProcessor.waitForTransactionReceipt(transactionHash); |
||||
assertThat(transactionReceiptResult.isStatusOK()).isTrue(); |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
0x8a15eaa6e526e9b8ebdea0bfc6d4399b0ae7ba1c |
@ -0,0 +1 @@ |
||||
0xc86c0030a86bb894c1e10c25d48d4949a288df9c6ff736b32d977f39893fd0dc |
@ -0,0 +1,45 @@ |
||||
{ |
||||
"config":{ |
||||
"homesteadBlock": 0, |
||||
"byzantiumBlock": 0, |
||||
"constantinopleBlock": 0, |
||||
"chainId":2020, |
||||
"eip150Block": 0, |
||||
"eip155Block": 0, |
||||
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"eip158Block": 0, |
||||
"clique":{ |
||||
"blockperiodseconds":2, |
||||
"epochlength":30000, |
||||
"period": 2, |
||||
"epoch": 30000 |
||||
}, |
||||
"isQuorum": true |
||||
}, |
||||
"coinbase":"0x0000000000000000000000000000000000000000", |
||||
|
||||
"extraData":"0x00000000000000000000000000000000000000000000000000000000000000008a15eaa6e526e9b8ebdea0bfc6d4399b0ae7ba1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"alloc": { |
||||
"fe3b557e8fb62b89f4916b721be55ceb828dbd73": { |
||||
"privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", |
||||
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", |
||||
"balance": "0xad78ebc5ac6200000" |
||||
}, |
||||
"627306090abaB3A6e1400e9345bC60c78a8BEf57": { |
||||
"privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", |
||||
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", |
||||
"balance": "90000000000000000000000" |
||||
}, |
||||
"f17f52151EbEF6C7334FAD080c5704D77216b732": { |
||||
"privateKey": "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f", |
||||
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", |
||||
"balance": "90000000000000000000000" |
||||
} |
||||
}, |
||||
"difficulty": "0x0", |
||||
"gasLimit": "0xE0000000", |
||||
"mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", |
||||
"nonce": "0x0", |
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"timestamp": "0x00" |
||||
} |
@ -0,0 +1 @@ |
||||
c0b7ff2352175d2a43d72eca7c2f806cc7bd74a7a597630d62f5261834348f74 |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"type" : "unlocked", |
||||
"data" : { |
||||
"bytes" : "srn2w10NWdvWyHkPMWB07W5NjIPmiAHRqESL8vH3Kyw=" |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
3XGBIf+x8IdVQOVfIsbRnHwTYOJP/Fx84G8gMmy8qDM= |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"type" : "unlocked", |
||||
"data" : { |
||||
"bytes" : "LWjzMnDP/MQIATegt45LTrMh1i9ISX64CFO0EzFDNhg=" |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
8JJLEAbq6o9m4Kqm++v0Y1n9Z2ryAFtZTyhnxSKWgws= |
@ -0,0 +1,47 @@ |
||||
{ |
||||
"useWhiteList": false, |
||||
"jdbc": { |
||||
"username": "sa", |
||||
"password": "", |
||||
"url": "jdbc:h2:/tmp/tessera;LOCK_TIMEOUT=20000", |
||||
"autoCreateTables": true |
||||
}, |
||||
"serverConfigs":[ |
||||
{ |
||||
"app":"ThirdParty", |
||||
"enabled": true, |
||||
"serverAddress": "http://localhost:9081", |
||||
"communicationType" : "REST" |
||||
}, |
||||
{ |
||||
"app":"Q2T", |
||||
"enabled": true, |
||||
"serverAddress": "unix:/tmp/test.ipc", |
||||
"communicationType" : "REST" |
||||
}, |
||||
{ |
||||
"app":"P2P", |
||||
"enabled": true, |
||||
"serverAddress":"http://localhost:9001", |
||||
"sslConfig": { |
||||
"tls": "OFF" |
||||
}, |
||||
"communicationType" : "REST" |
||||
} |
||||
], |
||||
"peer": [ |
||||
{ |
||||
"url": "http://localhost:9001" |
||||
} |
||||
], |
||||
"keys": { |
||||
"passwords": [], |
||||
"keyData": [ |
||||
{ |
||||
"privateKeyPath": "myKey.key", |
||||
"publicKeyPath": "myKey.pub" |
||||
} |
||||
] |
||||
}, |
||||
"alwaysSendTo": [] |
||||
} |
@ -0,0 +1,9 @@ |
||||
pragma solidity >=0.6.0 < 0.8.0; |
||||
|
||||
contract TestContract { |
||||
event TestEvent(address indexed _from, int val); |
||||
|
||||
function logEvent(int randomNum) public { |
||||
emit TestEvent(msg.sender, randomNum); |
||||
} |
||||
} |
Loading…
Reference in new issue