diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/EthConditions.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/EthConditions.java index 81fc2330a6..982ca4145b 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/EthConditions.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/EthConditions.java @@ -48,6 +48,17 @@ public Condition expectNoTransactionReceipt(final String transactionHash) { public Condition sendRawTransactionExceptional( final String transactionData, final String expectedMessage) { return new ExpectEthSendRawTransactionException( - transactions.sendRawTransactionTransaction(transactionData), expectedMessage); + transactions.sendRawTransaction(transactionData), expectedMessage); + } + + public Condition expectSuccessfulTransactionReceiptWithReason( + final String transactionHash, final String revertReason) { + return new ExpectSuccessfulEthGetTransactionReceiptWithReason( + transactions.getTransactionReceiptWithRevertReason(transactionHash), revertReason); + } + + public Condition expectSuccessfulTransactionReceiptWithoutReason(final String transactionHash) { + return new ExpectSuccessfulEthGetTransactionReceiptWithoutReason( + transactions.getTransactionReceiptWithRevertReason(transactionHash)); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthGetTransactionReceiptWithReason.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthGetTransactionReceiptWithReason.java new file mode 100644 index 0000000000..8dffef90b0 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthGetTransactionReceiptWithReason.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 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. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils; +import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthGetTransactionReceiptWithRevertReason; + +public class ExpectSuccessfulEthGetTransactionReceiptWithReason implements Condition { + + private final EthGetTransactionReceiptWithRevertReason transaction; + private final String expectedRevertReason; + + public ExpectSuccessfulEthGetTransactionReceiptWithReason( + final EthGetTransactionReceiptWithRevertReason transaction, + final String expectedRevertReason) { + this.transaction = transaction; + this.expectedRevertReason = expectedRevertReason; + } + + @Override + public void verify(final Node node) { + WaitUtils.waitFor(() -> assertThat(revertReasonMatches(node, expectedRevertReason)).isTrue()); + } + + private boolean revertReasonMatches(final Node node, final String expectedRevertReason) { + return node.execute(transaction) + .filter( + transactionReceipt -> + transactionReceipt.getRevertReason().contains(expectedRevertReason)) + .isPresent(); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthGetTransactionReceiptWithoutReason.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthGetTransactionReceiptWithoutReason.java new file mode 100644 index 0000000000..75a45ee0ce --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthGetTransactionReceiptWithoutReason.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 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. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils; +import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthGetTransactionReceiptWithRevertReason; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.CustomRequestFactory.TransactionReceiptWithRevertReason; + +public class ExpectSuccessfulEthGetTransactionReceiptWithoutReason implements Condition { + + private final EthGetTransactionReceiptWithRevertReason transaction; + + public ExpectSuccessfulEthGetTransactionReceiptWithoutReason( + final EthGetTransactionReceiptWithRevertReason transaction) { + this.transaction = transaction; + } + + @Override + public void verify(final Node node) { + WaitUtils.waitFor(() -> assertThat(revertReasonIsEmpty(node)).isTrue()); + } + + private boolean revertReasonIsEmpty(final Node node) { + return node.execute(transaction) + .map(TransactionReceiptWithRevertReason::getRevertReason) + .filter(String::isEmpty) + .isPresent(); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index 894bbfc0cc..25c687efe3 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -37,7 +37,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaRequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.ibft2.Ibft2RequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.login.LoginRequestFactory; -import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.CustomNetJsonRpcRequestFactory; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.CustomRequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermissioningJsonRpcRequestFactory; import java.io.File; @@ -80,6 +80,7 @@ public class PantheonNode implements NodeConfiguration, RunnableNode, AutoClosea private final Properties portsProperties = new Properties(); private final Boolean p2pEnabled; private final NetworkingConfiguration networkingConfiguration; + private final boolean revertReasonEnabled; private final String name; private final MiningParameters miningParameters; @@ -117,10 +118,12 @@ public PantheonNode( final NetworkingConfiguration networkingConfiguration, final boolean discoveryEnabled, final boolean bootnodeEligible, + final boolean revertReasonEnabled, final List plugins, final List extraCLIOptions) throws IOException { this.bootnodeEligible = bootnodeEligible; + this.revertReasonEnabled = revertReasonEnabled; this.homeDirectory = Files.createTempDirectory("acctest"); keyfilePath.ifPresent( path -> { @@ -287,7 +290,7 @@ private NodeRequests nodeRequests() { new PermissioningJsonRpcRequestFactory(web3jService), new AdminRequestFactory(web3jService), new EeaRequestFactory(web3jService), - new CustomNetJsonRpcRequestFactory(web3jService), + new CustomRequestFactory(web3jService), websocketService, loginRequestFactory()); } @@ -521,6 +524,11 @@ public List getExtraCLIOptions() { return extraCLIOptions; } + @Override + public boolean isRevertReasonEnabled() { + return revertReasonEnabled; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java index 10da4b6972..6d23468e39 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java @@ -150,6 +150,10 @@ public void startNode(final PantheonNode node) { params.addAll(networkConfigParams); } + if (node.isRevertReasonEnabled()) { + params.add("--revert-reason-enabled"); + } + node.getPermissioningConfiguration() .flatMap(PermissioningConfiguration::getLocalConfig) .ifPresent( diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java index 96c1908dc0..51ffdcad65 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java @@ -118,6 +118,7 @@ public void startNode(final PantheonNode node) { .rocksDbConfiguration(RocksDbConfiguration.builder().databaseDir(tempDir).build()) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .clock(Clock.systemUTC()) + .isRevertReasonEnabled(node.isRevertReasonEnabled()) .build(); } catch (final IOException e) { throw new RuntimeException("Error building PantheonController", e); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java index 36621e2592..ce86b46886 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java @@ -47,4 +47,6 @@ public interface NodeConfiguration { boolean isBootnodeEligible(); List getExtraCLIOptions(); + + boolean isRevertReasonEnabled(); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java index d3aea06527..fa68bba920 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java @@ -40,6 +40,7 @@ public class PantheonFactoryConfiguration { private final NetworkingConfiguration networkingConfiguration; private final boolean discoveryEnabled; private final boolean bootnodeEligible; + private final boolean revertReasonEnabled; private final List plugins; private final List extraCLIOptions; @@ -58,6 +59,7 @@ public PantheonFactoryConfiguration( final NetworkingConfiguration networkingConfiguration, final boolean discoveryEnabled, final boolean bootnodeEligible, + final boolean revertReasonEnabled, final List plugins, final List extraCLIOptions) { this.name = name; @@ -74,6 +76,7 @@ public PantheonFactoryConfiguration( this.networkingConfiguration = networkingConfiguration; this.discoveryEnabled = discoveryEnabled; this.bootnodeEligible = bootnodeEligible; + this.revertReasonEnabled = revertReasonEnabled; this.plugins = plugins; this.extraCLIOptions = extraCLIOptions; } @@ -141,4 +144,8 @@ public List getPlugins() { public List getExtraCLIOptions() { return extraCLIOptions; } + + public boolean isRevertReasonEnabled() { + return revertReasonEnabled; + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java index c0bec820f1..3337181e43 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java @@ -49,6 +49,7 @@ public class PantheonFactoryConfigurationBuilder { private NetworkingConfiguration networkingConfiguration = NetworkingConfiguration.create(); private boolean discoveryEnabled = true; private boolean bootnodeEligible = true; + private boolean revertReasonEnabled = false; private List plugins = new ArrayList<>(); private List extraCLIOptions = new ArrayList<>(); @@ -187,6 +188,11 @@ public PantheonFactoryConfigurationBuilder extraCLIOptions(final List ex return this; } + public PantheonFactoryConfigurationBuilder revertReasonEnabled() { + this.revertReasonEnabled = true; + return this; + } + public PantheonFactoryConfiguration build() { return new PantheonFactoryConfiguration( name, @@ -203,6 +209,7 @@ public PantheonFactoryConfiguration build() { networkingConfiguration, discoveryEnabled, bootnodeEligible, + revertReasonEnabled, plugins, extraCLIOptions); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java index 9606263d87..3eb1d1669c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java @@ -47,6 +47,7 @@ public PantheonNode create(final PantheonFactoryConfiguration config) throws IOE config.getNetworkingConfiguration(), config.isDiscoveryEnabled(), config.isBootnodeEligible(), + config.isRevertReasonEnabled(), config.getPlugins(), config.getExtraCLIOptions()); } @@ -61,6 +62,17 @@ public PantheonNode createMinerNode(final String name) throws IOException { .build()); } + public PantheonNode createMinerNodeWithRevertReasonEnabled(final String name) throws IOException { + return create( + new PantheonFactoryConfigurationBuilder() + .name(name) + .miningEnabled() + .jsonRpcEnabled() + .webSocketEnabled() + .revertReasonEnabled() + .build()); + } + public PantheonNode createArchiveNode(final String name) throws IOException { return create( new PantheonFactoryConfigurationBuilder() diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java index f9fd3aac1b..1d3fbd5fc9 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java @@ -45,6 +45,7 @@ public class PrivacyPantheonFactoryConfiguration extends PantheonFactoryConfigur final NetworkingConfiguration networkingConfiguration, final boolean discoveryEnabled, final boolean bootnodeEligible, + final boolean revertReasonEnabled, final List plugins, final List extraCLIOptions, final OrionTestHarness orion) { @@ -63,6 +64,7 @@ public class PrivacyPantheonFactoryConfiguration extends PantheonFactoryConfigur networkingConfiguration, discoveryEnabled, bootnodeEligible, + revertReasonEnabled, plugins, extraCLIOptions); this.orion = orion; diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfigurationBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfigurationBuilder.java index 34aeef9639..c66417f54b 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfigurationBuilder.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfigurationBuilder.java @@ -47,6 +47,7 @@ public PrivacyPantheonFactoryConfiguration build() { config.getNetworkingConfiguration(), config.isDiscoveryEnabled(), config.isBootnodeEligible(), + config.isRevertReasonEnabled(), config.getPlugins(), config.getExtraCLIOptions(), orion); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonNodeFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonNodeFactory.java index 9971d92f2f..6eb0651072 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonNodeFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonNodeFactory.java @@ -43,6 +43,7 @@ private static PrivacyNode create(final PrivacyPantheonFactoryConfiguration conf config.getNetworkingConfiguration(), config.isDiscoveryEnabled(), config.isBootnodeEligible(), + config.isRevertReasonEnabled(), config.getPlugins(), config.getExtraCLIOptions(), config.getOrion()); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java index 867fe2371c..7cd47d1fce 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java @@ -59,6 +59,7 @@ public PrivacyNode( final NetworkingConfiguration networkingConfiguration, final boolean discoveryEnabled, final boolean bootnodeEligible, + final boolean revertReasonEnabled, final List plugins, final List extraCLIOptions, final OrionTestHarness orion) @@ -78,6 +79,7 @@ public PrivacyNode( networkingConfiguration, discoveryEnabled, bootnodeEligible, + revertReasonEnabled, plugins, extraCLIOptions); this.orion = orion; diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/CallSmartContractFunction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/CallSmartContractFunction.java new file mode 100644 index 0000000000..ea692bfc0d --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/CallSmartContractFunction.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 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. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.transaction; + +import tech.pegasys.pantheon.tests.acceptance.dsl.account.Accounts; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Collections; + +import org.web3j.abi.FunctionEncoder; +import org.web3j.abi.datatypes.Function; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.core.methods.response.EthSendTransaction; +import org.web3j.tx.RawTransactionManager; + +public class CallSmartContractFunction implements Transaction { + + private static final BigInteger GAS_PRICE = BigInteger.valueOf(1000); + private static final BigInteger GAS_LIMIT = BigInteger.valueOf(3000000); + private static final Credentials BENEFACTOR_ONE = + Credentials.create(Accounts.GENESIS_ACCOUNT_ONE_PRIVATE_KEY); + + private final String functionName; + private final String contractAddress; + + public CallSmartContractFunction(final String functionName, final String contractAddress) { + this.functionName = functionName; + this.contractAddress = contractAddress; + } + + @Override + public EthSendTransaction execute(final NodeRequests node) { + final Function function = + new Function(functionName, Collections.emptyList(), Collections.emptyList()); + final RawTransactionManager transactionManager = + new RawTransactionManager(node.eth(), BENEFACTOR_ONE); + try { + return transactionManager.sendTransaction( + GAS_PRICE, GAS_LIMIT, contractAddress, FunctionEncoder.encode(function), BigInteger.ZERO); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/NodeRequests.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/NodeRequests.java index c375901a0a..247859588a 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/NodeRequests.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/NodeRequests.java @@ -17,34 +17,34 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaRequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.ibft2.Ibft2RequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.login.LoginRequestFactory; -import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.CustomNetJsonRpcRequestFactory; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.CustomRequestFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermissioningJsonRpcRequestFactory; import java.util.Optional; -import org.web3j.protocol.core.JsonRpc2_0Web3j; +import org.web3j.protocol.Web3j; import org.web3j.protocol.websocket.WebSocketService; public class NodeRequests { - private final JsonRpc2_0Web3j netEth; + private final Web3j netEth; private final CliqueRequestFactory clique; private final Ibft2RequestFactory ibft; private final PermissioningJsonRpcRequestFactory perm; private final AdminRequestFactory admin; private final EeaRequestFactory eea; - private final CustomNetJsonRpcRequestFactory customNet; + private final CustomRequestFactory custom; private final Optional websocketService; private final LoginRequestFactory login; public NodeRequests( - final JsonRpc2_0Web3j netEth, + final Web3j netEth, final CliqueRequestFactory clique, final Ibft2RequestFactory ibft, final PermissioningJsonRpcRequestFactory perm, final AdminRequestFactory admin, final EeaRequestFactory eea, - final CustomNetJsonRpcRequestFactory customNet, + final CustomRequestFactory custom, final Optional websocketService, final LoginRequestFactory login) { this.netEth = netEth; @@ -53,16 +53,16 @@ public NodeRequests( this.perm = perm; this.admin = admin; this.eea = eea; - this.customNet = customNet; + this.custom = custom; this.websocketService = websocketService; this.login = login; } - public JsonRpc2_0Web3j eth() { + public Web3j eth() { return netEth; } - public JsonRpc2_0Web3j net() { + public Web3j net() { return netEth; } @@ -82,8 +82,8 @@ public AdminRequestFactory admin() { return admin; } - public CustomNetJsonRpcRequestFactory customNet() { - return customNet; + public CustomRequestFactory custom() { + return custom; } public EeaRequestFactory eea() { diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/contract/ContractTransactions.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/contract/ContractTransactions.java index 3a917dc4b4..2b36437b73 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/contract/ContractTransactions.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/contract/ContractTransactions.java @@ -12,6 +12,7 @@ */ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.contract; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.CallSmartContractFunction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.DeploySmartContractTransaction; import org.web3j.tx.Contract; @@ -22,4 +23,9 @@ public DeploySmartContractTransaction createSmartContrac final Class clazz) { return new DeploySmartContractTransaction<>(clazz); } + + public CallSmartContractFunction callSmartContract( + final String functionName, final String contractAddress) { + return new CallSmartContractFunction(functionName, contractAddress); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptWithRevertReason.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptWithRevertReason.java new file mode 100644 index 0000000000..748ae145ee --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptWithRevertReason.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 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. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.NodeRequests; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.CustomRequestFactory.EthGetTransactionReceiptWithRevertReasonResponse; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.CustomRequestFactory.TransactionReceiptWithRevertReason; + +import java.io.IOException; +import java.util.Optional; + +public class EthGetTransactionReceiptWithRevertReason + implements Transaction> { + private final String transactionHash; + + public EthGetTransactionReceiptWithRevertReason(final String transactionHash) { + this.transactionHash = transactionHash; + } + + @Override + public Optional execute(final NodeRequests node) { + try { + final EthGetTransactionReceiptWithRevertReasonResponse response = + node.custom().ethGetTransactionReceiptWithRevertReason(transactionHash).send(); + assertThat(response.hasError()).isFalse(); + return Optional.ofNullable(response.getResult()); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthTransactions.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthTransactions.java index 7b6f66db18..807b2f132f 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthTransactions.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthTransactions.java @@ -47,12 +47,16 @@ public EthGetTransactionReceiptTransaction getTransactionReceipt(final String tr return new EthGetTransactionReceiptTransaction(transactionHash); } - public EthSendRawTransactionTransaction sendRawTransactionTransaction( - final String transactionData) { + public EthSendRawTransactionTransaction sendRawTransaction(final String transactionData) { return new EthSendRawTransactionTransaction(transactionData); } public EthGetTransactionCountTransaction getTransactionCount(final String accountAddress) { return new EthGetTransactionCountTransaction(accountAddress); } + + public EthGetTransactionReceiptWithRevertReason getTransactionReceiptWithRevertReason( + final String transactionHash) { + return new EthGetTransactionReceiptWithRevertReason(transactionHash); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/CustomNetJsonRpcRequestFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/CustomRequestFactory.java similarity index 54% rename from acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/CustomNetJsonRpcRequestFactory.java rename to acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/CustomRequestFactory.java index bfd9e5a96f..8cb5ed9055 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/CustomNetJsonRpcRequestFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/CustomRequestFactory.java @@ -18,14 +18,31 @@ import org.web3j.protocol.Web3jService; import org.web3j.protocol.core.Request; import org.web3j.protocol.core.Response; +import org.web3j.protocol.core.methods.response.TransactionReceipt; -public class CustomNetJsonRpcRequestFactory { +public class CustomRequestFactory { + private final Web3jService web3jService; public static class NetServicesResponse extends Response>> {} - private final Web3jService web3jService; + public static class TransactionReceiptWithRevertReason extends TransactionReceipt { + private String revertReason; + + public TransactionReceiptWithRevertReason() {} + + public void setRevertReason(final String revertReason) { + this.revertReason = revertReason; + } + + public String getRevertReason() { + return revertReason; + } + } + + public static class EthGetTransactionReceiptWithRevertReasonResponse + extends Response {} - public CustomNetJsonRpcRequestFactory(final Web3jService web3jService) { + public CustomRequestFactory(final Web3jService web3jService) { this.web3jService = web3jService; } @@ -33,4 +50,13 @@ public Request netServices() { return new Request<>( "net_services", Collections.emptyList(), web3jService, NetServicesResponse.class); } + + public Request + ethGetTransactionReceiptWithRevertReason(final String transactionHash) { + return new Request<>( + "eth_getTransactionReceipt", + Collections.singletonList(transactionHash), + web3jService, + EthGetTransactionReceiptWithRevertReasonResponse.class); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java index bfd6ac0833..1503aa3772 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java @@ -25,11 +25,10 @@ public class NetServicesTransaction implements Transaction> execute(final NodeRequests requestFactories) { - CustomNetJsonRpcRequestFactory.NetServicesResponse netServicesResponse = null; + CustomRequestFactory.NetServicesResponse netServicesResponse = null; try { - final CustomNetJsonRpcRequestFactory netServicesJsonRpcRequestFactory = - requestFactories.customNet(); - final Request request = + final CustomRequestFactory netServicesJsonRpcRequestFactory = requestFactories.custom(); + final Request request = netServicesJsonRpcRequestFactory.netServices(); netServicesResponse = request.send(); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/RevertReason.sol b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/RevertReason.sol new file mode 100644 index 0000000000..e4c6247379 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/RevertReason.sol @@ -0,0 +1,28 @@ +/* + * Copyright 2019 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. + */ +pragma solidity >=0.4.0 <0.6.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 tech.pegasys.pantheon.tests.web3j.generated +contract RevertReason { + + function revertWithRevertReason() public pure returns (bool) { + revert("RevertReason"); + } + + function revertWithoutRevertReason() public pure returns (bool) { + revert(); + } +} \ No newline at end of file diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/RevertReasonAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/RevertReasonAcceptanceTest.java new file mode 100644 index 0000000000..c50ea845f8 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/RevertReasonAcceptanceTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 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. + */ +package tech.pegasys.pantheon.tests.web3j; + +import static tech.pegasys.pantheon.tests.web3j.generated.RevertReason.FUNC_REVERTWITHOUTREVERTREASON; +import static tech.pegasys.pantheon.tests.web3j.generated.RevertReason.FUNC_REVERTWITHREVERTREASON; + +import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; +import tech.pegasys.pantheon.tests.web3j.generated.RevertReason; + +import org.junit.Before; +import org.junit.Test; +import org.web3j.protocol.core.methods.response.EthSendTransaction; + +public class RevertReasonAcceptanceTest extends AcceptanceTestBase { + + private PantheonNode minerNode; + + @Before + public void setUp() throws Exception { + minerNode = pantheon.createMinerNodeWithRevertReasonEnabled("miner-node-withRevertReason"); + cluster.start(minerNode); + } + + @Test + public void mustRevertWithRevertReason() { + final RevertReason revertReasonContract = + minerNode.execute(contractTransactions.createSmartContract(RevertReason.class)); + final EthSendTransaction transaction = + minerNode.execute( + contractTransactions.callSmartContract( + FUNC_REVERTWITHREVERTREASON, revertReasonContract.getContractAddress())); + minerNode.verify( + eth.expectSuccessfulTransactionReceiptWithReason( + transaction.getTransactionHash(), "RevertReason")); + } + + @Test + public void mustRevertWithoutRevertReason() { + final RevertReason revertReasonContract = + minerNode.execute(contractTransactions.createSmartContract(RevertReason.class)); + final EthSendTransaction transaction = + minerNode.execute( + contractTransactions.callSmartContract( + FUNC_REVERTWITHOUTREVERTREASON, revertReasonContract.getContractAddress())); + minerNode.verify( + eth.expectSuccessfulTransactionReceiptWithoutReason(transaction.getTransactionHash())); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.abi b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.abi new file mode 100644 index 0000000000..e1bf8b0463 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"revertWithRevertReason","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"revertWithoutRevertReason","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.bin b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.bin new file mode 100644 index 0000000000..1848ca10c1 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5060d18061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806311f95f6f146037578063ff489d31146051575b600080fd5b603d6057565b604080519115158252519081900360200190f35b603d6095565b6040805162461bcd60e51b815260206004820152600c60248201526b2932bb32b93a2932b0b9b7b760a11b6044820152905160009181900360640190fd5b6000806000fdfea265627a7a723058202dd24b599e57aa54899e1beceec3fb4a5001fccb4be994e8d18aa03cc123708764736f6c634300050a0032 \ No newline at end of file diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.java new file mode 100644 index 0000000000..965bf43aec --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.java @@ -0,0 +1,166 @@ +/* + * Copyright 2019 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. + */ +package tech.pegasys.pantheon.tests.web3j.generated; + +import java.math.BigInteger; +import java.util.Arrays; + +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +/** + * Auto generated code. + * + *

Do not modify! + * + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the codegen module to update. + * + *

Generated with web3j version 4.3.0. + */ +@SuppressWarnings("rawtypes") +public class RevertReason extends Contract { + private static final String BINARY = + "608060405234801561001057600080fd5b5060d18061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806311f95f6f146037578063ff489d31146051575b600080fd5b603d6057565b604080519115158252519081900360200190f35b603d6095565b6040805162461bcd60e51b815260206004820152600c60248201526b2932bb32b93a2932b0b9b7b760a11b6044820152905160009181900360640190fd5b6000806000fdfea265627a7a723058202dd24b599e57aa54899e1beceec3fb4a5001fccb4be994e8d18aa03cc123708764736f6c634300050a0032"; + + public static final String FUNC_REVERTWITHREVERTREASON = "revertWithRevertReason"; + + public static final String FUNC_REVERTWITHOUTREVERTREASON = "revertWithoutRevertReason"; + + @Deprecated + protected RevertReason( + String contractAddress, + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected RevertReason( + String contractAddress, + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected RevertReason( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected RevertReason( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public RemoteCall revertWithRevertReason() { + final Function function = + new Function( + FUNC_REVERTWITHREVERTREASON, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteCall revertWithoutRevertReason() { + final Function function = + new Function( + FUNC_REVERTWITHOUTREVERTREASON, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + @Deprecated + public static RevertReason load( + String contractAddress, + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit) { + return new RevertReason(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static RevertReason load( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + return new RevertReason(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static RevertReason load( + String contractAddress, + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider) { + return new RevertReason(contractAddress, web3j, credentials, contractGasProvider); + } + + public static RevertReason load( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { + return new RevertReason(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy( + Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + return deployRemoteCall( + RevertReason.class, web3j, credentials, contractGasProvider, BINARY, ""); + } + + @Deprecated + public static RemoteCall deploy( + Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return deployRemoteCall(RevertReason.class, web3j, credentials, gasPrice, gasLimit, BINARY, ""); + } + + public static RemoteCall deploy( + Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return deployRemoteCall( + RevertReason.class, web3j, transactionManager, contractGasProvider, BINARY, ""); + } + + @Deprecated + public static RemoteCall deploy( + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + return deployRemoteCall( + RevertReason.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, ""); + } +} diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolSchedule.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolSchedule.java index 6fd0224f6d..dfc98d684e 100644 --- a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolSchedule.java +++ b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolSchedule.java @@ -39,7 +39,8 @@ public class CliqueProtocolSchedule { public static ProtocolSchedule create( final GenesisConfigOptions config, final KeyPair nodeKeys, - final PrivacyParameters privacyParameters) { + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { final CliqueConfigOptions cliqueConfig = config.getCliqueConfigOptions(); @@ -52,13 +53,16 @@ public static ProtocolSchedule create( builder -> applyCliqueSpecificModifications( epochManager, cliqueConfig.getBlockPeriodSeconds(), localNodeAddress, builder), - privacyParameters) + privacyParameters, + isRevertReasonEnabled) .createProtocolSchedule(); } public static ProtocolSchedule create( - final GenesisConfigOptions config, final KeyPair nodeKeys) { - return create(config, nodeKeys, PrivacyParameters.DEFAULT); + final GenesisConfigOptions config, + final KeyPair nodeKeys, + final boolean isRevertReasonEnabled) { + return create(config, nodeKeys, PrivacyParameters.DEFAULT, isRevertReasonEnabled); } private static ProtocolSpecBuilder applyCliqueSpecificModifications( diff --git a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolScheduleTest.java b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolScheduleTest.java index fcec092441..cccaca8427 100644 --- a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolScheduleTest.java +++ b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolScheduleTest.java @@ -41,7 +41,7 @@ public void protocolSpecsAreCreatedAtBlockDefinedInJson() { final GenesisConfigOptions config = GenesisConfigFile.fromConfig(jsonInput).getConfigOptions(); final ProtocolSchedule protocolSchedule = - CliqueProtocolSchedule.create(config, NODE_KEYS); + CliqueProtocolSchedule.create(config, NODE_KEYS, false); final ProtocolSpec homesteadSpec = protocolSchedule.getByBlockNumber(1); final ProtocolSpec tangerineWhistleSpec = protocolSchedule.getByBlockNumber(2); @@ -56,7 +56,8 @@ public void protocolSpecsAreCreatedAtBlockDefinedInJson() { @Test public void parametersAlignWithMainnetWithAdjustments() { final ProtocolSpec homestead = - CliqueProtocolSchedule.create(GenesisConfigFile.DEFAULT.getConfigOptions(), NODE_KEYS) + CliqueProtocolSchedule.create( + GenesisConfigFile.DEFAULT.getConfigOptions(), NODE_KEYS, false) .getByBlockNumber(0); assertThat(homestead.getName()).isEqualTo("Frontier"); diff --git a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockCreatorTest.java b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockCreatorTest.java index abb4c9a09a..fbc7133bb7 100644 --- a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockCreatorTest.java +++ b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockCreatorTest.java @@ -76,7 +76,7 @@ public class CliqueBlockCreatorTest { public void setup() { protocolSchedule = CliqueProtocolSchedule.create( - GenesisConfigFile.DEFAULT.getConfigOptions(), proposerKeyPair); + GenesisConfigFile.DEFAULT.getConfigOptions(), proposerKeyPair, false); final Address otherAddress = Util.publicKeyToAddress(otherKeyPair.getPublicKey()); validatorList.add(otherAddress); diff --git a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java index 24b41d9ebf..19012ab83c 100644 --- a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java +++ b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java @@ -90,7 +90,7 @@ public void extraDataCreatedOnEpochBlocksContainsValidators() { new CliqueMinerExecutor( cliqueProtocolContext, Executors.newSingleThreadExecutor(), - CliqueProtocolSchedule.create(GENESIS_CONFIG_OPTIONS, proposerKeyPair), + CliqueProtocolSchedule.create(GENESIS_CONFIG_OPTIONS, proposerKeyPair, false), new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, @@ -127,7 +127,7 @@ public void extraDataForNonEpochBlocksDoesNotContainValidaors() { new CliqueMinerExecutor( cliqueProtocolContext, Executors.newSingleThreadExecutor(), - CliqueProtocolSchedule.create(GENESIS_CONFIG_OPTIONS, proposerKeyPair), + CliqueProtocolSchedule.create(GENESIS_CONFIG_OPTIONS, proposerKeyPair, false), new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, @@ -164,7 +164,7 @@ public void shouldUseLatestVanityData() { new CliqueMinerExecutor( cliqueProtocolContext, Executors.newSingleThreadExecutor(), - CliqueProtocolSchedule.create(GENESIS_CONFIG_OPTIONS, proposerKeyPair), + CliqueProtocolSchedule.create(GENESIS_CONFIG_OPTIONS, proposerKeyPair, false), new PendingTransactions( TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftProtocolSchedule.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftProtocolSchedule.java index 02b63fb199..d9526928d2 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftProtocolSchedule.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftProtocolSchedule.java @@ -33,7 +33,9 @@ public class IbftProtocolSchedule { private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE; public static ProtocolSchedule create( - final GenesisConfigOptions config, final PrivacyParameters privacyParameters) { + final GenesisConfigOptions config, + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { final IbftConfigOptions ibftConfig = config.getIbftLegacyConfigOptions(); final long blockPeriod = ibftConfig.getBlockPeriodSeconds(); @@ -41,12 +43,18 @@ public static ProtocolSchedule create( config, DEFAULT_CHAIN_ID, builder -> applyIbftChanges(blockPeriod, builder), - privacyParameters) + privacyParameters, + isRevertReasonEnabled) .createProtocolSchedule(); } + public static ProtocolSchedule create( + final GenesisConfigOptions config, final boolean isRevertReasonEnabled) { + return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled); + } + public static ProtocolSchedule create(final GenesisConfigOptions config) { - return create(config, PrivacyParameters.DEFAULT); + return create(config, PrivacyParameters.DEFAULT, false); } private static ProtocolSpecBuilder applyIbftChanges( diff --git a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java b/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java index 136717e511..321557b9c0 100644 --- a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java +++ b/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java @@ -34,7 +34,9 @@ public class IbftProtocolSchedule { private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE; public static ProtocolSchedule create( - final GenesisConfigOptions config, final PrivacyParameters privacyParameters) { + final GenesisConfigOptions config, + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { final IbftConfigOptions ibftConfig = config.getIbftLegacyConfigOptions(); final long blockPeriod = ibftConfig.getBlockPeriodSeconds(); @@ -42,12 +44,14 @@ public static ProtocolSchedule create( config, DEFAULT_CHAIN_ID, builder -> applyIbftChanges(blockPeriod, builder), - privacyParameters) + privacyParameters, + isRevertReasonEnabled) .createProtocolSchedule(); } - public static ProtocolSchedule create(final GenesisConfigOptions config) { - return create(config, PrivacyParameters.DEFAULT); + public static ProtocolSchedule create( + final GenesisConfigOptions config, final boolean isRevertReasonEnabled) { + return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled); } private static ProtocolSpecBuilder applyIbftChanges( diff --git a/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java b/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java index a2eb08fc6a..2163e0fe71 100644 --- a/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java +++ b/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java @@ -81,7 +81,8 @@ public void headerProducedPassesValidationRules() { final ProtocolSchedule protocolSchedule = IbftProtocolSchedule.create( GenesisConfigFile.fromConfig("{\"config\": {\"spuriousDragonBlock\":0}}") - .getConfigOptions()); + .getConfigOptions(), + false); final ProtocolContext protContext = new ProtocolContext<>( blockchain, diff --git a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java index 3d16861f14..346101f9cf 100644 --- a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java @@ -57,6 +57,7 @@ import java.math.BigInteger; import java.time.Instant; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.collect.Lists; @@ -128,7 +129,9 @@ public void failedTransactionsAreIncludedInTheBlock() { when(transactionProcessor.processTransaction( any(), any(), any(), eq(transaction), any(), any(), anyBoolean(), any())) - .thenReturn(MainnetTransactionProcessor.Result.failed(5, ValidationResult.valid())); + .thenReturn( + MainnetTransactionProcessor.Result.failed( + 5, ValidationResult.valid(), Optional.empty())); // The block should fit 3 transactions only final ProcessableBlockHeader blockHeader = createBlockWithGasLimit(5000); @@ -527,7 +530,8 @@ private Transaction createTransaction(final int transactionNumber) { // This is a duplicate of the MainnetProtocolSpec::frontierTransactionReceiptFactory private TransactionReceipt createReceipt( final TransactionProcessor.Result result, final WorldState worldState, final long gasUsed) { - return new TransactionReceipt(worldState.rootHash(), gasUsed, Lists.newArrayList()); + return new TransactionReceipt( + worldState.rootHash(), gasUsed, Lists.newArrayList(), Optional.empty()); } private DefaultMutableWorldState inMemoryWorldState() { diff --git a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java index 83c192cbe1..ad80f58bd9 100644 --- a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java @@ -57,7 +57,8 @@ public class EthHashBlockCreatorTest { GenesisConfigFile.DEFAULT.getConfigOptions(), BigInteger.valueOf(42), Function.identity(), - PrivacyParameters.DEFAULT) + PrivacyParameters.DEFAULT, + false) .createProtocolSchedule()) .build(); diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionReceipt.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionReceipt.java index 872bc6bfc8..a72c15eaa8 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionReceipt.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionReceipt.java @@ -13,11 +13,15 @@ package tech.pegasys.pantheon.ethereum.core; import tech.pegasys.pantheon.ethereum.mainnet.TransactionReceiptType; +import tech.pegasys.pantheon.ethereum.rlp.RLPException; import tech.pegasys.pantheon.ethereum.rlp.RLPInput; import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; +import tech.pegasys.pantheon.util.bytes.BytesValue; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; +import java.util.Optional; import com.google.common.base.MoreObjects; @@ -42,6 +46,7 @@ public class TransactionReceipt { private final LogsBloomFilter bloomFilter; private final int status; private final TransactionReceiptType transactionReceiptType; + private final Optional revertReason; /** * Creates an instance of a state root-encoded transaction receipt. @@ -49,18 +54,29 @@ public class TransactionReceipt { * @param stateRoot the state root for the world state after the transaction has been processed * @param cumulativeGasUsed the total amount of gas consumed in the block after this transaction * @param logs the logs generated within the transaction + * @param revertReason the revert reason for a failed transaction (if applicable) */ public TransactionReceipt( - final Hash stateRoot, final long cumulativeGasUsed, final List logs) { - this(stateRoot, NONEXISTENT, cumulativeGasUsed, logs, LogsBloomFilter.compute(logs)); + final Hash stateRoot, + final long cumulativeGasUsed, + final List logs, + final Optional revertReason) { + this( + stateRoot, + NONEXISTENT, + cumulativeGasUsed, + logs, + LogsBloomFilter.compute(logs), + revertReason); } private TransactionReceipt( final Hash stateRoot, final long cumulativeGasUsed, final List logs, - final LogsBloomFilter bloomFilter) { - this(stateRoot, NONEXISTENT, cumulativeGasUsed, logs, bloomFilter); + final LogsBloomFilter bloomFilter, + final Optional revertReason) { + this(stateRoot, NONEXISTENT, cumulativeGasUsed, logs, bloomFilter, revertReason); } /** @@ -69,17 +85,23 @@ private TransactionReceipt( * @param status the status code for the transaction (1 for success and 0 for failure) * @param cumulativeGasUsed the total amount of gas consumed in the block after this transaction * @param logs the logs generated within the transaction + * @param revertReason the revert reason for a failed transaction (if applicable) */ - public TransactionReceipt(final int status, final long cumulativeGasUsed, final List logs) { - this(null, status, cumulativeGasUsed, logs, LogsBloomFilter.compute(logs)); + public TransactionReceipt( + final int status, + final long cumulativeGasUsed, + final List logs, + final Optional revertReason) { + this(null, status, cumulativeGasUsed, logs, LogsBloomFilter.compute(logs), revertReason); } private TransactionReceipt( final int status, final long cumulativeGasUsed, final List logs, - final LogsBloomFilter bloomFilter) { - this(null, status, cumulativeGasUsed, logs, bloomFilter); + final LogsBloomFilter bloomFilter, + final Optional revertReason) { + this(null, status, cumulativeGasUsed, logs, bloomFilter, revertReason); } private TransactionReceipt( @@ -87,7 +109,8 @@ private TransactionReceipt( final int status, final long cumulativeGasUsed, final List logs, - final LogsBloomFilter bloomFilter) { + final LogsBloomFilter bloomFilter, + final Optional revertReason) { this.stateRoot = stateRoot; this.cumulativeGasUsed = cumulativeGasUsed; this.status = status; @@ -95,6 +118,7 @@ private TransactionReceipt( this.bloomFilter = bloomFilter; transactionReceiptType = stateRoot == null ? TransactionReceiptType.STATUS : TransactionReceiptType.ROOT; + this.revertReason = revertReason; } /** @@ -103,6 +127,14 @@ private TransactionReceipt( * @param out The RLP output to write to */ public void writeTo(final RLPOutput out) { + writeTo(out, false); + } + + public void writeToWithRevertReason(final RLPOutput out) { + writeTo(out, true); + } + + private void writeTo(final RLPOutput out, final boolean withRevertReason) { out.startList(); // Determine whether it's a state root-encoded transaction receipt @@ -115,7 +147,9 @@ public void writeTo(final RLPOutput out) { out.writeLongScalar(cumulativeGasUsed); out.writeBytesValue(bloomFilter.getBytes()); out.writeList(logs, Log::writeTo); - + if (withRevertReason && revertReason.isPresent()) { + out.writeBytesValue(BytesValue.wrap(revertReason.get().getBytes(StandardCharsets.UTF_8))); + } out.endList(); } @@ -126,6 +160,17 @@ public void writeTo(final RLPOutput out) { * @return the transaction receipt */ public static TransactionReceipt readFrom(final RLPInput input) { + return readFrom(input, true); + } + /** + * Creates a transaction receipt for the given RLP + * + * @param input the RLP-encoded transaction receipt + * @param revertReasonAllowed whether the rlp input is allowed to have a revert reason + * @return the transaction receipt + */ + public static TransactionReceipt readFrom( + final RLPInput input, final boolean revertReasonAllowed) { input.enterList(); try { @@ -137,15 +182,25 @@ public static TransactionReceipt readFrom(final RLPInput input) { // TODO consider validating that the logs and bloom filter match. final LogsBloomFilter bloomFilter = LogsBloomFilter.readFrom(input); final List logs = input.readList(Log::readFrom); + final Optional revertReason; + if (input.isEndOfCurrentList()) { + revertReason = Optional.empty(); + } else { + if (!revertReasonAllowed) { + throw new RLPException("Unexpected value at end of TransactionReceipt"); + } + final byte[] bytes = input.readBytesValue().getArrayUnsafe(); + revertReason = Optional.of(new String(bytes, StandardCharsets.UTF_8)); + } // Status code-encoded transaction receipts have a single // byte for success (0x01) or failure (0x80). if (firstElement.raw().size() == 1) { final int status = firstElement.readIntScalar(); - return new TransactionReceipt(status, cumulativeGas, logs, bloomFilter); + return new TransactionReceipt(status, cumulativeGas, logs, bloomFilter, revertReason); } else { final Hash stateRoot = Hash.wrap(firstElement.readBytes32()); - return new TransactionReceipt(stateRoot, cumulativeGas, logs, bloomFilter); + return new TransactionReceipt(stateRoot, cumulativeGas, logs, bloomFilter, revertReason); } } finally { input.leaveList(); @@ -201,6 +256,10 @@ public TransactionReceiptType getTransactionReceiptType() { return transactionReceiptType; } + public Optional getRevertReason() { + return revertReason; + } + @Override public boolean equals(final Object obj) { if (obj == this) { diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java index 24a30ab34b..d9df25023d 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java @@ -21,15 +21,23 @@ public class FixedDifficultyProtocolSchedule { public static ProtocolSchedule create( - final GenesisConfigOptions config, final PrivacyParameters privacyParameters) { + final GenesisConfigOptions config, + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { return new ProtocolScheduleBuilder<>( config, builder -> builder.difficultyCalculator(FixedDifficultyCalculators.calculator(config)), - privacyParameters) + privacyParameters, + isRevertReasonEnabled) .createProtocolSchedule(); } + public static ProtocolSchedule create( + final GenesisConfigOptions config, final boolean isRevertReasonEnabled) { + return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled); + } + public static ProtocolSchedule create(final GenesisConfigOptions config) { - return create(config, PrivacyParameters.DEFAULT); + return create(config, PrivacyParameters.DEFAULT, false); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSchedule.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSchedule.java index cb0005f985..181203303f 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSchedule.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSchedule.java @@ -27,7 +27,8 @@ public class MainnetProtocolSchedule { public static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE; public static ProtocolSchedule create() { - return fromConfig(GenesisConfigFile.mainnet().getConfigOptions(), PrivacyParameters.DEFAULT); + return fromConfig( + GenesisConfigFile.mainnet().getConfigOptions(), PrivacyParameters.DEFAULT, false); } /** @@ -36,18 +37,35 @@ public static ProtocolSchedule create() { * @param config {@link GenesisConfigOptions} containing the config options for the milestone * starting points * @param privacyParameters the parameters set for private transactions + * @param isRevertReasonEnabled whether storing the revert reason is for failed transactions * @return A configured mainnet protocol schedule */ public static ProtocolSchedule fromConfig( - final GenesisConfigOptions config, final PrivacyParameters privacyParameters) { + final GenesisConfigOptions config, + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { if (FixedDifficultyCalculators.isFixedDifficultyInConfig(config)) { - return FixedDifficultyProtocolSchedule.create(config, privacyParameters); + return FixedDifficultyProtocolSchedule.create( + config, privacyParameters, isRevertReasonEnabled); } return new ProtocolScheduleBuilder<>( - config, DEFAULT_CHAIN_ID, Function.identity(), privacyParameters) + config, DEFAULT_CHAIN_ID, Function.identity(), privacyParameters, isRevertReasonEnabled) .createProtocolSchedule(); } + /** + * Create a Mainnet protocol schedule from a config object + * + * @param config {@link GenesisConfigOptions} containing the config options for the milestone + * starting points + * @param isRevertReasonEnabled whether storing the revert reason is for failed transactions + * @return A configured mainnet protocol schedule + */ + public static ProtocolSchedule fromConfig( + final GenesisConfigOptions config, final boolean isRevertReasonEnabled) { + return fromConfig(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled); + } + /** * Create a Mainnet protocol schedule from a config object * @@ -56,6 +74,6 @@ public static ProtocolSchedule fromConfig( * @return A configured mainnet protocol schedule */ public static ProtocolSchedule fromConfig(final GenesisConfigOptions config) { - return fromConfig(config, PrivacyParameters.DEFAULT); + return fromConfig(config, PrivacyParameters.DEFAULT, false); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java index 625c80e3f4..ac7f43c6b0 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java @@ -224,12 +224,16 @@ public static ProtocolSpecBuilder spuriousDragonDefinition( public static ProtocolSpecBuilder byzantiumDefinition( final Optional chainId, final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit) { + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason) { return spuriousDragonDefinition(chainId, contractSizeLimit, configStackSizeLimit) .evmBuilder(MainnetEvmRegistries::byzantium) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::byzantium) .difficultyCalculator(MainnetDifficultyCalculators.BYZANTIUM) - .transactionReceiptFactory(MainnetProtocolSpecs::byzantiumTransactionReceiptFactory) + .transactionReceiptFactory( + enableRevertReason + ? MainnetProtocolSpecs::byzantiumTransactionReceiptFactoryWithReasonEnabled + : MainnetProtocolSpecs::byzantiumTransactionReceiptFactory) .blockReward(BYZANTIUM_BLOCK_REWARD) .transactionReceiptType(TransactionReceiptType.STATUS) .name("Byzantium"); @@ -238,8 +242,9 @@ public static ProtocolSpecBuilder byzantiumDefinition( public static ProtocolSpecBuilder constantinopleDefinition( final Optional chainId, final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit) { - return byzantiumDefinition(chainId, contractSizeLimit, configStackSizeLimit) + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason) { + return byzantiumDefinition(chainId, contractSizeLimit, configStackSizeLimit, enableRevertReason) .difficultyCalculator(MainnetDifficultyCalculators.CONSTANTINOPLE) .gasCalculator(ConstantinopleGasCalculator::new) .evmBuilder(MainnetEvmRegistries::constantinople) @@ -250,8 +255,10 @@ public static ProtocolSpecBuilder constantinopleDefinition( public static ProtocolSpecBuilder constantinopleFixDefinition( final Optional chainId, final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit) { - return constantinopleDefinition(chainId, contractSizeLimit, configStackSizeLimit) + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason) { + return constantinopleDefinition( + chainId, contractSizeLimit, configStackSizeLimit, enableRevertReason) .gasCalculator(ConstantinopleFixGasCalculator::new) .name("ConstantinopleFix"); } @@ -259,19 +266,32 @@ public static ProtocolSpecBuilder constantinopleFixDefinition( public static ProtocolSpecBuilder istanbulDefinition( final Optional chainId, final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit) { - return constantinopleFixDefinition(chainId, contractSizeLimit, configStackSizeLimit) + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason) { + return constantinopleFixDefinition( + chainId, contractSizeLimit, configStackSizeLimit, enableRevertReason) .name("Istanbul"); } private static TransactionReceipt frontierTransactionReceiptFactory( final TransactionProcessor.Result result, final WorldState worldState, final long gasUsed) { - return new TransactionReceipt(worldState.rootHash(), gasUsed, result.getLogs()); + return new TransactionReceipt( + worldState.rootHash(), + gasUsed, + result.getLogs(), + Optional.empty()); // No revert reason in frontier } private static TransactionReceipt byzantiumTransactionReceiptFactory( final TransactionProcessor.Result result, final WorldState worldState, final long gasUsed) { - return new TransactionReceipt(result.isSuccessful() ? 1 : 0, gasUsed, result.getLogs()); + return new TransactionReceipt( + result.isSuccessful() ? 1 : 0, gasUsed, result.getLogs(), Optional.empty()); + } + + private static TransactionReceipt byzantiumTransactionReceiptFactoryWithReasonEnabled( + final TransactionProcessor.Result result, final WorldState worldState, final long gasUsed) { + return new TransactionReceipt( + result.isSuccessful() ? 1 : 0, gasUsed, result.getLogs(), result.getRevertReason()); } private static class DaoBlockProcessor implements BlockProcessor { diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionProcessor.java index f3b07e3c97..d19e2f9944 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionProcessor.java @@ -32,6 +32,7 @@ import java.util.ArrayDeque; import java.util.Deque; +import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -60,17 +61,30 @@ public static class Result implements TransactionProcessor.Result { private final BytesValue output; private final ValidationResult validationResult; + private final Optional revertReason; public static Result invalid( final ValidationResult validationResult) { - return new Result(Status.INVALID, LogSeries.empty(), -1, BytesValue.EMPTY, validationResult); + return new Result( + Status.INVALID, + LogSeries.empty(), + -1, + BytesValue.EMPTY, + validationResult, + Optional.empty()); } public static Result failed( final long gasRemaining, - final ValidationResult validationResult) { + final ValidationResult validationResult, + final Optional revertReason) { return new Result( - Status.FAILED, LogSeries.empty(), gasRemaining, BytesValue.EMPTY, validationResult); + Status.FAILED, + LogSeries.empty(), + gasRemaining, + BytesValue.EMPTY, + validationResult, + revertReason); } public static Result successful( @@ -78,7 +92,8 @@ public static Result successful( final long gasRemaining, final BytesValue output, final ValidationResult validationResult) { - return new Result(Status.SUCCESSFUL, logs, gasRemaining, output, validationResult); + return new Result( + Status.SUCCESSFUL, logs, gasRemaining, output, validationResult, Optional.empty()); } Result( @@ -86,12 +101,14 @@ public static Result successful( final LogSeries logs, final long gasRemaining, final BytesValue output, - final ValidationResult validationResult) { + final ValidationResult validationResult, + final Optional revertReason) { this.status = status; this.logs = logs; this.gasRemaining = gasRemaining; this.output = output; this.validationResult = validationResult; + this.revertReason = revertReason; } @Override @@ -118,6 +135,11 @@ public BytesValue getOutput() { public ValidationResult getValidationResult() { return validationResult; } + + @Override + public Optional getRevertReason() { + return revertReason; + } } private final boolean clearEmptyAccounts; @@ -296,7 +318,7 @@ public Result processTransaction( initialFrame.getOutputData(), validationResult); } else { - return Result.failed(refunded.toLong(), validationResult); + return Result.failed(refunded.toLong(), validationResult, initialFrame.getRevertReason()); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolScheduleBuilder.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolScheduleBuilder.java index d0b74de04e..b4ed838f48 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolScheduleBuilder.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolScheduleBuilder.java @@ -29,31 +29,41 @@ public class ProtocolScheduleBuilder { private final Function, ProtocolSpecBuilder> protocolSpecAdapter; private final Optional defaultChainId; private final PrivacyParameters privacyParameters; + private final boolean isRevertReasonEnabled; public ProtocolScheduleBuilder( final GenesisConfigOptions config, final BigInteger defaultChainId, final Function, ProtocolSpecBuilder> protocolSpecAdapter, - final PrivacyParameters privacyParameters) { - this(config, Optional.of(defaultChainId), protocolSpecAdapter, privacyParameters); + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { + this( + config, + Optional.of(defaultChainId), + protocolSpecAdapter, + privacyParameters, + isRevertReasonEnabled); } public ProtocolScheduleBuilder( final GenesisConfigOptions config, final Function, ProtocolSpecBuilder> protocolSpecAdapter, - final PrivacyParameters privacyParameters) { - this(config, Optional.empty(), protocolSpecAdapter, privacyParameters); + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { + this(config, Optional.empty(), protocolSpecAdapter, privacyParameters, isRevertReasonEnabled); } private ProtocolScheduleBuilder( final GenesisConfigOptions config, final Optional defaultChainId, final Function, ProtocolSpecBuilder> protocolSpecAdapter, - final PrivacyParameters privacyParameters) { + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled) { this.config = config; this.defaultChainId = defaultChainId; this.protocolSpecAdapter = protocolSpecAdapter; this.privacyParameters = privacyParameters; + this.isRevertReasonEnabled = isRevertReasonEnabled; } public ProtocolSchedule createProtocolSchedule() { @@ -109,22 +119,34 @@ public ProtocolSchedule createProtocolSchedule() { protocolSchedule, config.getByzantiumBlockNumber(), MainnetProtocolSpecs.byzantiumDefinition( - chainId, config.getContractSizeLimit(), config.getEvmStackSize())); + chainId, + config.getContractSizeLimit(), + config.getEvmStackSize(), + isRevertReasonEnabled)); addProtocolSpec( protocolSchedule, config.getConstantinopleBlockNumber(), MainnetProtocolSpecs.constantinopleDefinition( - chainId, config.getContractSizeLimit(), config.getEvmStackSize())); + chainId, + config.getContractSizeLimit(), + config.getEvmStackSize(), + isRevertReasonEnabled)); addProtocolSpec( protocolSchedule, config.getConstantinopleFixBlockNumber(), MainnetProtocolSpecs.constantinopleFixDefinition( - chainId, config.getContractSizeLimit(), config.getEvmStackSize())); + chainId, + config.getContractSizeLimit(), + config.getEvmStackSize(), + isRevertReasonEnabled)); addProtocolSpec( protocolSchedule, config.getIstanbulBlockNumber(), MainnetProtocolSpecs.istanbulDefinition( - chainId, config.getContractSizeLimit(), config.getEvmStackSize())); + chainId, + config.getContractSizeLimit(), + config.getEvmStackSize(), + isRevertReasonEnabled)); LOG.info("Protocol schedule created with milestones: {}", protocolSchedule.listMilestones()); return protocolSchedule; diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionProcessor.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionProcessor.java index 88556fb29e..7d2931b48b 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionProcessor.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionProcessor.java @@ -25,6 +25,8 @@ import tech.pegasys.pantheon.ethereum.vm.OperationTracer; import tech.pegasys.pantheon.util.bytes.BytesValue; +import java.util.Optional; + /** Processes transactions. */ public interface TransactionProcessor { @@ -95,6 +97,13 @@ default boolean isSuccessful() { * @return the validation result, with the reason for failure (if applicable.) */ ValidationResult getValidationResult(); + + /** + * Returns the reason why a transaction was reverted (if applicable). + * + * @return the revert reason. + */ + Optional getRevertReason(); } /** diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java index 180f9e8398..bc8451ca91 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java @@ -38,6 +38,7 @@ import java.util.ArrayDeque; import java.util.Deque; +import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -69,17 +70,30 @@ public static class Result implements TransactionProcessor.Result { private final BytesValue output; private final ValidationResult validationResult; + private final Optional revertReason; public static Result invalid( final ValidationResult validationResult) { - return new Result(Status.INVALID, LogSeries.empty(), -1, BytesValue.EMPTY, validationResult); + return new Result( + Status.INVALID, + LogSeries.empty(), + -1, + BytesValue.EMPTY, + validationResult, + Optional.empty()); } public static Result failed( final long gasRemaining, - final ValidationResult validationResult) { + final ValidationResult validationResult, + final Optional revertReason) { return new Result( - Status.FAILED, LogSeries.empty(), gasRemaining, BytesValue.EMPTY, validationResult); + Status.FAILED, + LogSeries.empty(), + gasRemaining, + BytesValue.EMPTY, + validationResult, + revertReason); } public static Result successful( @@ -87,7 +101,8 @@ public static Result successful( final long gasRemaining, final BytesValue output, final ValidationResult validationResult) { - return new Result(Status.SUCCESSFUL, logs, gasRemaining, output, validationResult); + return new Result( + Status.SUCCESSFUL, logs, gasRemaining, output, validationResult, Optional.empty()); } Result( @@ -95,12 +110,14 @@ public static Result successful( final LogSeries logs, final long gasRemaining, final BytesValue output, - final ValidationResult validationResult) { + final ValidationResult validationResult, + final Optional revertReason) { this.status = status; this.logs = logs; this.gasRemaining = gasRemaining; this.output = output; this.validationResult = validationResult; + this.revertReason = revertReason; } @Override @@ -127,6 +144,11 @@ public BytesValue getOutput() { public ValidationResult getValidationResult() { return validationResult; } + + @Override + public Optional getRevertReason() { + return revertReason; + } } @SuppressWarnings("unused") @@ -264,7 +286,9 @@ public Result processTransaction( initialFrame.getLogs(), 0, initialFrame.getOutputData(), ValidationResult.valid()); } else { return Result.failed( - 0, ValidationResult.invalid(TransactionInvalidReason.PRIVATE_TRANSACTION_FAILED)); + 0, + ValidationResult.invalid(TransactionInvalidReason.PRIVATE_TRANSACTION_FAILED), + initialFrame.getRevertReason()); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java index 749c6c1386..0fdf3600cf 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java @@ -201,7 +201,7 @@ private void remove(final BytesValue prefix, final BytesValue key) { } private BytesValue rlpEncode(final List receipts) { - return RLP.encode(o -> o.writeList(receipts, TransactionReceipt::writeTo)); + return RLP.encode(o -> o.writeList(receipts, TransactionReceipt::writeToWithRevertReason)); } } } diff --git a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/BlockDataGenerator.java b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/BlockDataGenerator.java index 9004b18dfc..3b46c2797b 100644 --- a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/BlockDataGenerator.java +++ b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/BlockDataGenerator.java @@ -298,7 +298,13 @@ public Set transactions(final int n) { } public TransactionReceipt receipt(final long cumulativeGasUsed) { - return new TransactionReceipt(hash(), cumulativeGasUsed, Arrays.asList(log(), log())); + return new TransactionReceipt( + hash(), cumulativeGasUsed, Arrays.asList(log(), log()), Optional.empty()); + } + + public TransactionReceipt receipt(final String revertReason) { + return new TransactionReceipt( + hash(), positiveLong(), Arrays.asList(log(), log()), Optional.of(revertReason)); } public TransactionReceipt receipt() { diff --git a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java index e545b2985d..7cef8004b0 100644 --- a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java +++ b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java @@ -113,7 +113,8 @@ public ExecutionContextTestFixture build() { new StubGenesisConfigOptions().istanbulBlock(0), BigInteger.valueOf(42), Function.identity(), - new PrivacyParameters()) + new PrivacyParameters(), + false) .createProtocolSchedule(); } if (keyValueStorage == null) { diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionReceiptTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionReceiptTest.java index 822a50c217..fb202cab87 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionReceiptTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionReceiptTest.java @@ -25,7 +25,16 @@ public void toFromRlp() { final BlockDataGenerator gen = new BlockDataGenerator(); final TransactionReceipt receipt = gen.receipt(); final TransactionReceipt copy = - TransactionReceipt.readFrom(RLP.input(RLP.encode(receipt::writeTo))); + TransactionReceipt.readFrom(RLP.input(RLP.encode(receipt::writeToWithRevertReason)), false); + assertEquals(receipt, copy); + } + + @Test + public void toFromRlpWithReason() { + final BlockDataGenerator gen = new BlockDataGenerator(); + final TransactionReceipt receipt = gen.receipt("RevertReason"); + final TransactionReceipt copy = + TransactionReceipt.readFrom(RLP.input(RLP.encode(receipt::writeToWithRevertReason))); assertEquals(receipt, copy); } } diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java index 82e27a3f27..df66f56073 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java @@ -17,7 +17,6 @@ import tech.pegasys.pantheon.config.GenesisConfigFile; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; -import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import org.junit.Test; @@ -28,8 +27,7 @@ public class FixedProtocolScheduleTest { public void reportedDifficultyForAllBlocksIsAFixedValue() { final ProtocolSchedule schedule = - FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions(), PrivacyParameters.DEFAULT); + FixedDifficultyProtocolSchedule.create(GenesisConfigFile.development().getConfigOptions()); final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolScheduleTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolScheduleTest.java index d499bd0a15..6834be93fc 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolScheduleTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolScheduleTest.java @@ -13,7 +13,6 @@ package tech.pegasys.pantheon.ethereum.mainnet; import tech.pegasys.pantheon.config.GenesisConfigFile; -import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import java.nio.charset.StandardCharsets; @@ -95,8 +94,7 @@ public void shouldCreateRopstenConfig() throws Exception { GenesisConfigFile.fromConfig( Resources.toString( this.getClass().getResource("/ropsten.json"), StandardCharsets.UTF_8)) - .getConfigOptions(), - PrivacyParameters.DEFAULT); + .getConfigOptions()); Assertions.assertThat(sched.getByBlockNumber(0).getName()).isEqualTo("TangerineWhistle"); Assertions.assertThat(sched.getByBlockNumber(1).getName()).isEqualTo("TangerineWhistle"); Assertions.assertThat(sched.getByBlockNumber(10).getName()).isEqualTo("SpuriousDragon"); diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/ReferenceTestProtocolSchedules.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/ReferenceTestProtocolSchedules.java index 483b8f7e6c..4d2ff6c6b9 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/ReferenceTestProtocolSchedules.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/ReferenceTestProtocolSchedules.java @@ -67,7 +67,7 @@ public ProtocolSchedule getByName(final String name) { private static ProtocolSchedule createSchedule(final GenesisConfigOptions options) { return new ProtocolScheduleBuilder<>( - options, CHAIN_ID, Function.identity(), PrivacyParameters.DEFAULT) + options, CHAIN_ID, Function.identity(), PrivacyParameters.DEFAULT, false) .createProtocolSchedule(); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/messages/ReceiptsMessage.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/messages/ReceiptsMessage.java index 666ff24a79..6c5015a9ae 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/messages/ReceiptsMessage.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/messages/ReceiptsMessage.java @@ -67,7 +67,7 @@ public List> receipts() { final int setSize = input.enterList(); final List receiptSet = new ArrayList<>(setSize); for (int i = 0; i < setSize; i++) { - receiptSet.add(TransactionReceipt.readFrom(input)); + receiptSet.add(TransactionReceipt.readFrom(input, false)); } input.leaveList(); receipts.add(receiptSet); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockBodiesMessageTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockBodiesMessageTest.java index 936dd75fa2..07318cdaf6 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockBodiesMessageTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockBodiesMessageTest.java @@ -66,7 +66,7 @@ public void blockBodiesRoundTrip() throws IOException { message .bodies( FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions())) + GenesisConfigFile.development().getConfigOptions(), false)) .iterator(); for (int i = 0; i < 50; ++i) { Assertions.assertThat(readBodies.next()).isEqualTo(bodies.get(i)); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockHeadersMessageTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockHeadersMessageTest.java index 8267cbba36..c018da2238 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockHeadersMessageTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockHeadersMessageTest.java @@ -58,7 +58,7 @@ public void blockHeadersRoundTrip() throws IOException { final List readHeaders = message.getHeaders( FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions())); + GenesisConfigFile.development().getConfigOptions(), false)); for (int i = 0; i < 50; ++i) { Assertions.assertThat(readHeaders.get(i)).isEqualTo(headers.get(i)); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTrackerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTrackerTest.java index 0176519ce7..fc8c011db9 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTrackerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTrackerTest.java @@ -44,7 +44,8 @@ public class ChainHeadTrackerTest { blockchain.getChainHead().getTotalDifficulty(), 0); private final ProtocolSchedule protocolSchedule = - FixedDifficultyProtocolSchedule.create(GenesisConfigFile.development().getConfigOptions()); + FixedDifficultyProtocolSchedule.create( + GenesisConfigFile.development().getConfigOptions(), false); private final TrailingPeerLimiter trailingPeerLimiter = mock(TrailingPeerLimiter.class); private final ChainHeadTracker chainHeadTracker = diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java index 862a96bf1c..8fd3d66ea7 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java @@ -94,7 +94,8 @@ public TestNode( final GenesisConfigFile genesisConfigFile = GenesisConfigFile.development(); final ProtocolSchedule protocolSchedule = - FixedDifficultyProtocolSchedule.create(GenesisConfigFile.development().getConfigOptions()); + FixedDifficultyProtocolSchedule.create( + GenesisConfigFile.development().getConfigOptions(), false); final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, protocolSchedule); final BlockHeaderFunctions blockHeaderFunctions = diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TransactionPoolTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TransactionPoolTest.java index faeaa55871..1f0dd485e5 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TransactionPoolTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TransactionPoolTest.java @@ -66,6 +66,7 @@ import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.Set; import org.junit.Before; @@ -638,7 +639,7 @@ private Block appendBlock( new BlockBody(transactionList, emptyList())); final List transactionReceipts = transactionList.stream() - .map(transaction -> new TransactionReceipt(1, 1, emptyList())) + .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) .collect(toList()); blockchain.appendBlock(block, transactionReceipts); return block; diff --git a/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java b/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java index edf78c4d00..cf21183470 100644 --- a/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java @@ -58,6 +58,7 @@ import java.math.BigInteger; import java.util.List; +import java.util.Optional; import org.assertj.core.util.Lists; import org.junit.Before; @@ -276,7 +277,7 @@ private Block appendBlock( new BlockBody(transactionList, emptyList())); final List transactionReceipts = transactionList.stream() - .map(transaction -> new TransactionReceipt(1, 1, emptyList())) + .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) .collect(toList()); blockchain.appendBlock(block, transactionReceipts); return block; diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/TransactionReceiptResult.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/TransactionReceiptResult.java index 0010ba17f7..77f2df95d1 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/TransactionReceiptResult.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/TransactionReceiptResult.java @@ -23,6 +23,7 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonPropertyOrder({ @@ -38,7 +39,8 @@ "status", "to", "transactionHash", - "transactionIndex" + "transactionIndex", + "revertReason" }) public abstract class TransactionReceiptResult { @@ -53,6 +55,7 @@ public abstract class TransactionReceiptResult { private final String to; private final String transactionHash; private final String transactionIndex; + private final String revertReason; protected final TransactionReceipt receipt; @@ -78,6 +81,7 @@ public TransactionReceiptResult(final TransactionReceiptWithMetadata receiptWith this.to = receiptWithMetadata.getTransaction().getTo().map(BytesValue::toString).orElse(null); this.transactionHash = receiptWithMetadata.getTransaction().hash().toString(); this.transactionIndex = Quantity.create(receiptWithMetadata.getTransactionIndex()); + this.revertReason = receipt.getRevertReason().orElse(null); } @JsonGetter(value = "blockHash") @@ -135,6 +139,12 @@ public String getTransactionIndex() { return transactionIndex; } + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonGetter(value = "revertReason") + public String getRevertReason() { + return revertReason; + } + private List logReceipts( final List logs, final long blockNumber, diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java index ae6a79aac1..e3f1810101 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java @@ -44,11 +44,11 @@ public class EthGetTransactionReceiptTest { private final TransactionReceipt stateReceipt = - new TransactionReceipt(1, 12, Collections.emptyList()); + new TransactionReceipt(1, 12, Collections.emptyList(), Optional.empty()); private final Hash stateRoot = Hash.fromHexString("0000000000000000000000000000000000000000000000000000000000000000"); private final TransactionReceipt rootReceipt = - new TransactionReceipt(stateRoot, 12, Collections.emptyList()); + new TransactionReceipt(stateRoot, 12, Collections.emptyList(), Optional.empty()); private final Signature signature = Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 1); private final Address sender = diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscriptionServiceTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscriptionServiceTest.java index a264926d04..2b5a4c2ef1 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscriptionServiceTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscriptionServiceTest.java @@ -195,7 +195,7 @@ private TransactionReceiptWithMetadata createTransactionWithLog( final Transaction transaction, final Log log) { final BlockHeader blockHeader = blockHeaderTestFixture.buildHeader(); final TransactionReceipt transactionReceipt = - new TransactionReceipt(Hash.ZERO, 1L, Lists.newArrayList(log)); + new TransactionReceipt(Hash.ZERO, 1L, Lists.newArrayList(log), Optional.empty()); final TransactionReceiptWithMetadata transactionReceiptWithMetadata = TransactionReceiptWithMetadata.create( transactionReceipt, diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index ed8aa9844d..572a565869 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -552,6 +552,12 @@ void setBannedNodeIds(final List values) { description = "Enable private transactions (default: ${DEFAULT-VALUE})") private final Boolean isPrivacyEnabled = false; + @Option( + names = {"--revert-reason-enabled"}, + description = + "Enable passing the revert reason back through TransactionReceipts (default: ${DEFAULT-VALUE})") + private final Boolean isRevertReasonEnabled = false; + @Option( names = {"--privacy-url"}, description = "The URL on which the enclave is running") @@ -846,6 +852,7 @@ public PantheonController buildController() { .metricsSystem(metricsSystem.get()) .privacyParameters(privacyParameters()) .clock(Clock.systemUTC()) + .isRevertReasonEnabled(isRevertReasonEnabled) .build(); } catch (final InvalidConfigurationException e) { throw new ExecutionException(this.commandLine, e.getMessage()); diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonControllerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonControllerBuilder.java index 392b570285..269b5e8427 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonControllerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonControllerBuilder.java @@ -121,7 +121,7 @@ protected MiningCoordinator createMiningCoordinator( @Override protected ProtocolSchedule createProtocolSchedule() { return CliqueProtocolSchedule.create( - genesisConfig.getConfigOptions(), nodeKeys, privacyParameters); + genesisConfig.getConfigOptions(), nodeKeys, privacyParameters, isRevertReasonEnabled); } @Override diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java index c8efc8e420..2112e81106 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java @@ -63,7 +63,8 @@ protected MiningCoordinator createMiningCoordinator( @Override protected ProtocolSchedule createProtocolSchedule() { - return IbftProtocolSchedule.create(genesisConfig.getConfigOptions(), privacyParameters); + return IbftProtocolSchedule.create( + genesisConfig.getConfigOptions(), privacyParameters, isRevertReasonEnabled); } @Override diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonControllerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonControllerBuilder.java index bd3a7111a4..e985947e52 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonControllerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonControllerBuilder.java @@ -210,7 +210,8 @@ protected MiningCoordinator createMiningCoordinator( @Override protected ProtocolSchedule createProtocolSchedule() { - return IbftProtocolSchedule.create(genesisConfig.getConfigOptions(), privacyParameters); + return IbftProtocolSchedule.create( + genesisConfig.getConfigOptions(), privacyParameters, isRevertReasonEnabled); } @Override diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonControllerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonControllerBuilder.java index ecfb9b9235..a77f17d5d5 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonControllerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonControllerBuilder.java @@ -85,6 +85,7 @@ protected Void createConsensusContext( @Override protected ProtocolSchedule createProtocolSchedule() { - return MainnetProtocolSchedule.fromConfig(genesisConfig.getConfigOptions(), privacyParameters); + return MainnetProtocolSchedule.fromConfig( + genesisConfig.getConfigOptions(), privacyParameters, isRevertReasonEnabled); } } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java index 413a41fc97..27352dcaa1 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java @@ -75,6 +75,7 @@ public abstract class PantheonControllerBuilder { protected Path dataDirectory; protected Clock clock; protected KeyPair nodeKeys; + protected boolean isRevertReasonEnabled; private StorageProvider storageProvider; private final List shutdownActions = new ArrayList<>(); private RocksDbConfiguration rocksDbConfiguration; @@ -154,6 +155,11 @@ public PantheonControllerBuilder transactionPoolConfiguration( return this; } + public PantheonControllerBuilder isRevertReasonEnabled(final boolean isRevertReasonEnabled) { + this.isRevertReasonEnabled = isRevertReasonEnabled; + return this; + } + public PantheonController build() throws IOException { checkNotNull(genesisConfig, "Missing genesis config"); checkNotNull(syncConfig, "Missing sync config"); diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java index 5fd5b951ff..58f2318bf8 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java @@ -141,6 +141,7 @@ public void initMocks() throws Exception { when(mockControllerBuilder.metricsSystem(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.privacyParameters(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.clock(any())).thenReturn(mockControllerBuilder); + when(mockControllerBuilder.isRevertReasonEnabled(false)).thenReturn(mockControllerBuilder); // doReturn used because of generic PantheonController doReturn(mockController).when(mockControllerBuilder).build(); diff --git a/pantheon/src/test/resources/everything_config.toml b/pantheon/src/test/resources/everything_config.toml index f13438d7ac..c8ea03d2c8 100644 --- a/pantheon/src/test/resources/everything_config.toml +++ b/pantheon/src/test/resources/everything_config.toml @@ -97,4 +97,7 @@ privacy-precompiled-address=9 tx-pool-retention-hours=999 tx-pool-max-size=1234 -Xincoming-tx-messages-keep-alive-seconds=60 \ No newline at end of file +Xincoming-tx-messages-keep-alive-seconds=60 + +# Revert Reason +revert-reason-enabled=false \ No newline at end of file