Include revert reason if available in eth_getTransactionReceipt (#1603)

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
pinges 5 years ago committed by Jason Frame
parent 690d3dd621
commit 895e526cec
  1. 13
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/EthConditions.java
  2. 46
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthGetTransactionReceiptWithReason.java
  3. 43
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectSuccessfulEthGetTransactionReceiptWithoutReason.java
  4. 12
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java
  5. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java
  6. 1
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java
  7. 2
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java
  8. 7
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java
  9. 7
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java
  10. 12
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java
  11. 2
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java
  12. 1
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfigurationBuilder.java
  13. 1
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonNodeFactory.java
  14. 2
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java
  15. 55
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/CallSmartContractFunction.java
  16. 22
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/NodeRequests.java
  17. 6
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/contract/ContractTransactions.java
  18. 44
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptWithRevertReason.java
  19. 8
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthTransactions.java
  20. 32
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/CustomRequestFactory.java
  21. 7
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java
  22. 28
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/RevertReason.sol
  23. 60
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/RevertReasonAcceptanceTest.java
  24. 1
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.abi
  25. 1
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.bin
  26. 166
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/generated/RevertReason.java
  27. 12
      consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolSchedule.java
  28. 5
      consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueProtocolScheduleTest.java
  29. 2
      consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockCreatorTest.java
  30. 6
      consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java
  31. 14
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftProtocolSchedule.java
  32. 12
      consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java
  33. 3
      consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/blockcreation/IbftBlockCreatorTest.java
  34. 8
      ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java
  35. 3
      ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java
  36. 83
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionReceipt.java
  37. 14
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java
  38. 28
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSchedule.java
  39. 40
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java
  40. 34
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionProcessor.java
  41. 40
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolScheduleBuilder.java
  42. 9
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionProcessor.java
  43. 36
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java
  44. 2
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java
  45. 8
      ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/BlockDataGenerator.java
  46. 3
      ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java
  47. 11
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionReceiptTest.java
  48. 4
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java
  49. 4
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolScheduleTest.java
  50. 2
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/ReferenceTestProtocolSchedules.java
  51. 2
      ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/messages/ReceiptsMessage.java
  52. 2
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockBodiesMessageTest.java
  53. 2
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/messages/BlockHeadersMessageTest.java
  54. 3
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTrackerTest.java
  55. 3
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java
  56. 3
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TransactionPoolTest.java
  57. 3
      ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/methods/EthGetFilterChangesIntegrationTest.java
  58. 12
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/TransactionReceiptResult.java
  59. 4
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java
  60. 2
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscriptionServiceTest.java
  61. 7
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
  62. 2
      pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonControllerBuilder.java
  63. 3
      pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java
  64. 3
      pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonControllerBuilder.java
  65. 3
      pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonControllerBuilder.java
  66. 6
      pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java
  67. 1
      pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
  68. 5
      pantheon/src/test/resources/everything_config.toml

@ -48,6 +48,17 @@ public class EthConditions {
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));
}
}

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

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

@ -37,7 +37,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueReque
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 class PantheonNode implements NodeConfiguration, RunnableNode, AutoClosea
final NetworkingConfiguration networkingConfiguration,
final boolean discoveryEnabled,
final boolean bootnodeEligible,
final boolean revertReasonEnabled,
final List<String> plugins,
final List<String> extraCLIOptions)
throws IOException {
this.bootnodeEligible = bootnodeEligible;
this.revertReasonEnabled = revertReasonEnabled;
this.homeDirectory = Files.createTempDirectory("acctest");
keyfilePath.ifPresent(
path -> {
@ -287,7 +290,7 @@ public class PantheonNode implements NodeConfiguration, RunnableNode, AutoClosea
new PermissioningJsonRpcRequestFactory(web3jService),
new AdminRequestFactory(web3jService),
new EeaRequestFactory(web3jService),
new CustomNetJsonRpcRequestFactory(web3jService),
new CustomRequestFactory(web3jService),
websocketService,
loginRequestFactory());
}
@ -521,6 +524,11 @@ public class PantheonNode implements NodeConfiguration, RunnableNode, AutoClosea
return extraCLIOptions;
}
@Override
public boolean isRevertReasonEnabled() {
return revertReasonEnabled;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)

@ -150,6 +150,10 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner {
params.addAll(networkConfigParams);
}
if (node.isRevertReasonEnabled()) {
params.add("--revert-reason-enabled");
}
node.getPermissioningConfiguration()
.flatMap(PermissioningConfiguration::getLocalConfig)
.ifPresent(

@ -118,6 +118,7 @@ public class ThreadPantheonNodeRunner implements PantheonNodeRunner {
.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);

@ -47,4 +47,6 @@ public interface NodeConfiguration {
boolean isBootnodeEligible();
List<String> getExtraCLIOptions();
boolean isRevertReasonEnabled();
}

@ -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<String> plugins;
private final List<String> extraCLIOptions;
@ -58,6 +59,7 @@ public class PantheonFactoryConfiguration {
final NetworkingConfiguration networkingConfiguration,
final boolean discoveryEnabled,
final boolean bootnodeEligible,
final boolean revertReasonEnabled,
final List<String> plugins,
final List<String> extraCLIOptions) {
this.name = name;
@ -74,6 +76,7 @@ public class PantheonFactoryConfiguration {
this.networkingConfiguration = networkingConfiguration;
this.discoveryEnabled = discoveryEnabled;
this.bootnodeEligible = bootnodeEligible;
this.revertReasonEnabled = revertReasonEnabled;
this.plugins = plugins;
this.extraCLIOptions = extraCLIOptions;
}
@ -141,4 +144,8 @@ public class PantheonFactoryConfiguration {
public List<String> getExtraCLIOptions() {
return extraCLIOptions;
}
public boolean isRevertReasonEnabled() {
return revertReasonEnabled;
}
}

@ -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<String> plugins = new ArrayList<>();
private List<String> extraCLIOptions = new ArrayList<>();
@ -187,6 +188,11 @@ public class PantheonFactoryConfigurationBuilder {
return this;
}
public PantheonFactoryConfigurationBuilder revertReasonEnabled() {
this.revertReasonEnabled = true;
return this;
}
public PantheonFactoryConfiguration build() {
return new PantheonFactoryConfiguration(
name,
@ -203,6 +209,7 @@ public class PantheonFactoryConfigurationBuilder {
networkingConfiguration,
discoveryEnabled,
bootnodeEligible,
revertReasonEnabled,
plugins,
extraCLIOptions);
}

@ -47,6 +47,7 @@ public class PantheonNodeFactory {
config.getNetworkingConfiguration(),
config.isDiscoveryEnabled(),
config.isBootnodeEligible(),
config.isRevertReasonEnabled(),
config.getPlugins(),
config.getExtraCLIOptions());
}
@ -61,6 +62,17 @@ public class PantheonNodeFactory {
.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()

@ -45,6 +45,7 @@ public class PrivacyPantheonFactoryConfiguration extends PantheonFactoryConfigur
final NetworkingConfiguration networkingConfiguration,
final boolean discoveryEnabled,
final boolean bootnodeEligible,
final boolean revertReasonEnabled,
final List<String> plugins,
final List<String> extraCLIOptions,
final OrionTestHarness orion) {
@ -63,6 +64,7 @@ public class PrivacyPantheonFactoryConfiguration extends PantheonFactoryConfigur
networkingConfiguration,
discoveryEnabled,
bootnodeEligible,
revertReasonEnabled,
plugins,
extraCLIOptions);
this.orion = orion;

@ -47,6 +47,7 @@ public class PrivacyPantheonFactoryConfigurationBuilder {
config.getNetworkingConfiguration(),
config.isDiscoveryEnabled(),
config.isBootnodeEligible(),
config.isRevertReasonEnabled(),
config.getPlugins(),
config.getExtraCLIOptions(),
orion);

@ -43,6 +43,7 @@ public class PrivacyPantheonNodeFactory {
config.getNetworkingConfiguration(),
config.isDiscoveryEnabled(),
config.isBootnodeEligible(),
config.isRevertReasonEnabled(),
config.getPlugins(),
config.getExtraCLIOptions(),
config.getOrion());

@ -59,6 +59,7 @@ public class PrivacyNode extends PantheonNode {
final NetworkingConfiguration networkingConfiguration,
final boolean discoveryEnabled,
final boolean bootnodeEligible,
final boolean revertReasonEnabled,
final List<String> plugins,
final List<String> extraCLIOptions,
final OrionTestHarness orion)
@ -78,6 +79,7 @@ public class PrivacyNode extends PantheonNode {
networkingConfiguration,
discoveryEnabled,
bootnodeEligible,
revertReasonEnabled,
plugins,
extraCLIOptions);
this.orion = orion;

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

@ -17,34 +17,34 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueReque
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> 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> websocketService,
final LoginRequestFactory login) {
this.netEth = netEth;
@ -53,16 +53,16 @@ public class 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 class NodeRequests {
return admin;
}
public CustomNetJsonRpcRequestFactory customNet() {
return customNet;
public CustomRequestFactory custom() {
return custom;
}
public EeaRequestFactory eea() {

@ -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 class ContractTransactions {
final Class<T> clazz) {
return new DeploySmartContractTransaction<>(clazz);
}
public CallSmartContractFunction callSmartContract(
final String functionName, final String contractAddress) {
return new CallSmartContractFunction(functionName, contractAddress);
}
}

@ -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<Optional<TransactionReceiptWithRevertReason>> {
private final String transactionHash;
public EthGetTransactionReceiptWithRevertReason(final String transactionHash) {
this.transactionHash = transactionHash;
}
@Override
public Optional<TransactionReceiptWithRevertReason> 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);
}
}
}

@ -47,12 +47,16 @@ public class EthTransactions {
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);
}
}

@ -18,14 +18,31 @@ import java.util.Map;
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<Map<String, Map<String, String>>> {}
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<TransactionReceiptWithRevertReason> {}
public CustomNetJsonRpcRequestFactory(final Web3jService web3jService) {
public CustomRequestFactory(final Web3jService web3jService) {
this.web3jService = web3jService;
}
@ -33,4 +50,13 @@ public class CustomNetJsonRpcRequestFactory {
return new Request<>(
"net_services", Collections.emptyList(), web3jService, NetServicesResponse.class);
}
public Request<?, EthGetTransactionReceiptWithRevertReasonResponse>
ethGetTransactionReceiptWithRevertReason(final String transactionHash) {
return new Request<>(
"eth_getTransactionReceipt",
Collections.singletonList(transactionHash),
web3jService,
EthGetTransactionReceiptWithRevertReasonResponse.class);
}
}

@ -25,11 +25,10 @@ public class NetServicesTransaction implements Transaction<Map<String, Map<Strin
@Override
public Map<String, Map<String, String>> execute(final NodeRequests requestFactories) {
CustomNetJsonRpcRequestFactory.NetServicesResponse netServicesResponse = null;
CustomRequestFactory.NetServicesResponse netServicesResponse = null;
try {
final CustomNetJsonRpcRequestFactory netServicesJsonRpcRequestFactory =
requestFactories.customNet();
final Request<?, CustomNetJsonRpcRequestFactory.NetServicesResponse> request =
final CustomRequestFactory netServicesJsonRpcRequestFactory = requestFactories.custom();
final Request<?, CustomRequestFactory.NetServicesResponse> request =
netServicesJsonRpcRequestFactory.netServices();
netServicesResponse = request.send();

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

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

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

@ -0,0 +1 @@
608060405234801561001057600080fd5b5060d18061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806311f95f6f146037578063ff489d31146051575b600080fd5b603d6057565b604080519115158252519081900360200190f35b603d6095565b6040805162461bcd60e51b815260206004820152600c60248201526b2932bb32b93a2932b0b9b7b760a11b6044820152905160009181900360640190fd5b6000806000fdfea265627a7a723058202dd24b599e57aa54899e1beceec3fb4a5001fccb4be994e8d18aa03cc123708764736f6c634300050a0032

@ -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.
*
* <p><strong>Do not modify!</strong>
*
* <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>,
* or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the <a
* href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
*
* <p>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<Boolean> revertWithRevertReason() {
final Function function =
new Function(
FUNC_REVERTWITHREVERTREASON,
Arrays.<Type>asList(),
Arrays.<TypeReference<?>>asList(new TypeReference<Bool>() {}));
return executeRemoteCallSingleValueReturn(function, Boolean.class);
}
public RemoteCall<Boolean> revertWithoutRevertReason() {
final Function function =
new Function(
FUNC_REVERTWITHOUTREVERTREASON,
Arrays.<Type>asList(),
Arrays.<TypeReference<?>>asList(new TypeReference<Bool>() {}));
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<RevertReason> deploy(
Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
return deployRemoteCall(
RevertReason.class, web3j, credentials, contractGasProvider, BINARY, "");
}
@Deprecated
public static RemoteCall<RevertReason> deploy(
Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return deployRemoteCall(RevertReason.class, web3j, credentials, gasPrice, gasLimit, BINARY, "");
}
public static RemoteCall<RevertReason> deploy(
Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
return deployRemoteCall(
RevertReason.class, web3j, transactionManager, contractGasProvider, BINARY, "");
}
@Deprecated
public static RemoteCall<RevertReason> deploy(
Web3j web3j,
TransactionManager transactionManager,
BigInteger gasPrice,
BigInteger gasLimit) {
return deployRemoteCall(
RevertReason.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, "");
}
}

@ -39,7 +39,8 @@ public class CliqueProtocolSchedule {
public static ProtocolSchedule<CliqueContext> 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 class CliqueProtocolSchedule {
builder ->
applyCliqueSpecificModifications(
epochManager, cliqueConfig.getBlockPeriodSeconds(), localNodeAddress, builder),
privacyParameters)
privacyParameters,
isRevertReasonEnabled)
.createProtocolSchedule();
}
public static ProtocolSchedule<CliqueContext> 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<CliqueContext> applyCliqueSpecificModifications(

@ -41,7 +41,7 @@ public class CliqueProtocolScheduleTest {
final GenesisConfigOptions config = GenesisConfigFile.fromConfig(jsonInput).getConfigOptions();
final ProtocolSchedule<CliqueContext> protocolSchedule =
CliqueProtocolSchedule.create(config, NODE_KEYS);
CliqueProtocolSchedule.create(config, NODE_KEYS, false);
final ProtocolSpec<CliqueContext> homesteadSpec = protocolSchedule.getByBlockNumber(1);
final ProtocolSpec<CliqueContext> tangerineWhistleSpec = protocolSchedule.getByBlockNumber(2);
@ -56,7 +56,8 @@ public class CliqueProtocolScheduleTest {
@Test
public void parametersAlignWithMainnetWithAdjustments() {
final ProtocolSpec<CliqueContext> homestead =
CliqueProtocolSchedule.create(GenesisConfigFile.DEFAULT.getConfigOptions(), NODE_KEYS)
CliqueProtocolSchedule.create(
GenesisConfigFile.DEFAULT.getConfigOptions(), NODE_KEYS, false)
.getByBlockNumber(0);
assertThat(homestead.getName()).isEqualTo("Frontier");

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

@ -90,7 +90,7 @@ public class CliqueMinerExecutorTest {
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 class CliqueMinerExecutorTest {
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 class CliqueMinerExecutorTest {
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,

@ -33,7 +33,9 @@ public class IbftProtocolSchedule {
private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE;
public static ProtocolSchedule<IbftContext> 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 class IbftProtocolSchedule {
config,
DEFAULT_CHAIN_ID,
builder -> applyIbftChanges(blockPeriod, builder),
privacyParameters)
privacyParameters,
isRevertReasonEnabled)
.createProtocolSchedule();
}
public static ProtocolSchedule<IbftContext> create(
final GenesisConfigOptions config, final boolean isRevertReasonEnabled) {
return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled);
}
public static ProtocolSchedule<IbftContext> create(final GenesisConfigOptions config) {
return create(config, PrivacyParameters.DEFAULT);
return create(config, PrivacyParameters.DEFAULT, false);
}
private static ProtocolSpecBuilder<IbftContext> applyIbftChanges(

@ -34,7 +34,9 @@ public class IbftProtocolSchedule {
private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE;
public static ProtocolSchedule<IbftContext> 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 class IbftProtocolSchedule {
config,
DEFAULT_CHAIN_ID,
builder -> applyIbftChanges(blockPeriod, builder),
privacyParameters)
privacyParameters,
isRevertReasonEnabled)
.createProtocolSchedule();
}
public static ProtocolSchedule<IbftContext> create(final GenesisConfigOptions config) {
return create(config, PrivacyParameters.DEFAULT);
public static ProtocolSchedule<IbftContext> create(
final GenesisConfigOptions config, final boolean isRevertReasonEnabled) {
return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled);
}
private static ProtocolSpecBuilder<IbftContext> applyIbftChanges(

@ -81,7 +81,8 @@ public class IbftBlockCreatorTest {
final ProtocolSchedule<IbftContext> protocolSchedule =
IbftProtocolSchedule.create(
GenesisConfigFile.fromConfig("{\"config\": {\"spuriousDragonBlock\":0}}")
.getConfigOptions());
.getConfigOptions(),
false);
final ProtocolContext<IbftContext> protContext =
new ProtocolContext<>(
blockchain,

@ -57,6 +57,7 @@ import tech.pegasys.pantheon.util.uint.UInt256;
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 class BlockTransactionSelectorTest {
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 @@ public class BlockTransactionSelectorTest {
// 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() {

@ -57,7 +57,8 @@ public class EthHashBlockCreatorTest {
GenesisConfigFile.DEFAULT.getConfigOptions(),
BigInteger.valueOf(42),
Function.identity(),
PrivacyParameters.DEFAULT)
PrivacyParameters.DEFAULT,
false)
.createProtocolSchedule())
.build();

@ -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<String> 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<Log> logs) {
this(stateRoot, NONEXISTENT, cumulativeGasUsed, logs, LogsBloomFilter.compute(logs));
final Hash stateRoot,
final long cumulativeGasUsed,
final List<Log> logs,
final Optional<String> revertReason) {
this(
stateRoot,
NONEXISTENT,
cumulativeGasUsed,
logs,
LogsBloomFilter.compute(logs),
revertReason);
}
private TransactionReceipt(
final Hash stateRoot,
final long cumulativeGasUsed,
final List<Log> logs,
final LogsBloomFilter bloomFilter) {
this(stateRoot, NONEXISTENT, cumulativeGasUsed, logs, bloomFilter);
final LogsBloomFilter bloomFilter,
final Optional<String> revertReason) {
this(stateRoot, NONEXISTENT, cumulativeGasUsed, logs, bloomFilter, revertReason);
}
/**
@ -69,17 +85,23 @@ public class 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<Log> logs) {
this(null, status, cumulativeGasUsed, logs, LogsBloomFilter.compute(logs));
public TransactionReceipt(
final int status,
final long cumulativeGasUsed,
final List<Log> logs,
final Optional<String> revertReason) {
this(null, status, cumulativeGasUsed, logs, LogsBloomFilter.compute(logs), revertReason);
}
private TransactionReceipt(
final int status,
final long cumulativeGasUsed,
final List<Log> logs,
final LogsBloomFilter bloomFilter) {
this(null, status, cumulativeGasUsed, logs, bloomFilter);
final LogsBloomFilter bloomFilter,
final Optional<String> revertReason) {
this(null, status, cumulativeGasUsed, logs, bloomFilter, revertReason);
}
private TransactionReceipt(
@ -87,7 +109,8 @@ public class TransactionReceipt {
final int status,
final long cumulativeGasUsed,
final List<Log> logs,
final LogsBloomFilter bloomFilter) {
final LogsBloomFilter bloomFilter,
final Optional<String> revertReason) {
this.stateRoot = stateRoot;
this.cumulativeGasUsed = cumulativeGasUsed;
this.status = status;
@ -95,6 +118,7 @@ public class TransactionReceipt {
this.bloomFilter = bloomFilter;
transactionReceiptType =
stateRoot == null ? TransactionReceiptType.STATUS : TransactionReceiptType.ROOT;
this.revertReason = revertReason;
}
/**
@ -103,6 +127,14 @@ public class 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 class TransactionReceipt {
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 class TransactionReceipt {
* @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 class TransactionReceipt {
// TODO consider validating that the logs and bloom filter match.
final LogsBloomFilter bloomFilter = LogsBloomFilter.readFrom(input);
final List<Log> logs = input.readList(Log::readFrom);
final Optional<String> 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 class TransactionReceipt {
return transactionReceiptType;
}
public Optional<String> getRevertReason() {
return revertReason;
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {

@ -21,15 +21,23 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolScheduleBuilder;
public class FixedDifficultyProtocolSchedule {
public static ProtocolSchedule<Void> 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<Void> create(
final GenesisConfigOptions config, final boolean isRevertReasonEnabled) {
return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled);
}
public static ProtocolSchedule<Void> create(final GenesisConfigOptions config) {
return create(config, PrivacyParameters.DEFAULT);
return create(config, PrivacyParameters.DEFAULT, false);
}
}

@ -27,7 +27,8 @@ public class MainnetProtocolSchedule {
public static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE;
public static ProtocolSchedule<Void> create() {
return fromConfig(GenesisConfigFile.mainnet().getConfigOptions(), PrivacyParameters.DEFAULT);
return fromConfig(
GenesisConfigFile.mainnet().getConfigOptions(), PrivacyParameters.DEFAULT, false);
}
/**
@ -36,18 +37,35 @@ public class MainnetProtocolSchedule {
* @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<Void> 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<Void> 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 class MainnetProtocolSchedule {
* @return A configured mainnet protocol schedule
*/
public static ProtocolSchedule<Void> fromConfig(final GenesisConfigOptions config) {
return fromConfig(config, PrivacyParameters.DEFAULT);
return fromConfig(config, PrivacyParameters.DEFAULT, false);
}
}

@ -224,12 +224,16 @@ public abstract class MainnetProtocolSpecs {
public static ProtocolSpecBuilder<Void> byzantiumDefinition(
final Optional<BigInteger> 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 abstract class MainnetProtocolSpecs {
public static ProtocolSpecBuilder<Void> constantinopleDefinition(
final Optional<BigInteger> 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 abstract class MainnetProtocolSpecs {
public static ProtocolSpecBuilder<Void> constantinopleFixDefinition(
final Optional<BigInteger> 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 abstract class MainnetProtocolSpecs {
public static ProtocolSpecBuilder<Void> istanbulDefinition(
final Optional<BigInteger> 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 {

@ -32,6 +32,7 @@ import tech.pegasys.pantheon.util.bytes.BytesValue;
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 class MainnetTransactionProcessor implements TransactionProcessor {
private final BytesValue output;
private final ValidationResult<TransactionInvalidReason> validationResult;
private final Optional<String> revertReason;
public static Result invalid(
final ValidationResult<TransactionInvalidReason> 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<TransactionInvalidReason> validationResult) {
final ValidationResult<TransactionInvalidReason> validationResult,
final Optional<String> 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 class MainnetTransactionProcessor implements TransactionProcessor {
final long gasRemaining,
final BytesValue output,
final ValidationResult<TransactionInvalidReason> 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 class MainnetTransactionProcessor implements TransactionProcessor {
final LogSeries logs,
final long gasRemaining,
final BytesValue output,
final ValidationResult<TransactionInvalidReason> validationResult) {
final ValidationResult<TransactionInvalidReason> validationResult,
final Optional<String> 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 class MainnetTransactionProcessor implements TransactionProcessor {
public ValidationResult<TransactionInvalidReason> getValidationResult() {
return validationResult;
}
@Override
public Optional<String> getRevertReason() {
return revertReason;
}
}
private final boolean clearEmptyAccounts;
@ -296,7 +318,7 @@ public class MainnetTransactionProcessor implements TransactionProcessor {
initialFrame.getOutputData(),
validationResult);
} else {
return Result.failed(refunded.toLong(), validationResult);
return Result.failed(refunded.toLong(), validationResult, initialFrame.getRevertReason());
}
}

@ -29,31 +29,41 @@ public class ProtocolScheduleBuilder<C> {
private final Function<ProtocolSpecBuilder<Void>, ProtocolSpecBuilder<C>> protocolSpecAdapter;
private final Optional<BigInteger> defaultChainId;
private final PrivacyParameters privacyParameters;
private final boolean isRevertReasonEnabled;
public ProtocolScheduleBuilder(
final GenesisConfigOptions config,
final BigInteger defaultChainId,
final Function<ProtocolSpecBuilder<Void>, ProtocolSpecBuilder<C>> 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<Void>, ProtocolSpecBuilder<C>> 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<BigInteger> defaultChainId,
final Function<ProtocolSpecBuilder<Void>, ProtocolSpecBuilder<C>> 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<C> createProtocolSchedule() {
@ -109,22 +119,34 @@ public class ProtocolScheduleBuilder<C> {
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;

@ -25,6 +25,8 @@ import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup;
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 @@ public interface TransactionProcessor {
* @return the validation result, with the reason for failure (if applicable.)
*/
ValidationResult<TransactionInvalidReason> getValidationResult();
/**
* Returns the reason why a transaction was reverted (if applicable).
*
* @return the revert reason.
*/
Optional<String> getRevertReason();
}
/**

@ -38,6 +38,7 @@ import tech.pegasys.pantheon.util.bytes.BytesValue;
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 class PrivateTransactionProcessor {
private final BytesValue output;
private final ValidationResult<TransactionInvalidReason> validationResult;
private final Optional<String> revertReason;
public static Result invalid(
final ValidationResult<TransactionInvalidReason> 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<TransactionInvalidReason> validationResult) {
final ValidationResult<TransactionInvalidReason> validationResult,
final Optional<String> 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 class PrivateTransactionProcessor {
final long gasRemaining,
final BytesValue output,
final ValidationResult<TransactionInvalidReason> 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 class PrivateTransactionProcessor {
final LogSeries logs,
final long gasRemaining,
final BytesValue output,
final ValidationResult<TransactionInvalidReason> validationResult) {
final ValidationResult<TransactionInvalidReason> validationResult,
final Optional<String> 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 class PrivateTransactionProcessor {
public ValidationResult<TransactionInvalidReason> getValidationResult() {
return validationResult;
}
@Override
public Optional<String> getRevertReason() {
return revertReason;
}
}
@SuppressWarnings("unused")
@ -264,7 +286,9 @@ public class PrivateTransactionProcessor {
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());
}
}

@ -201,7 +201,7 @@ public class KeyValueStoragePrefixedKeyBlockchainStorage implements BlockchainSt
}
private BytesValue rlpEncode(final List<TransactionReceipt> receipts) {
return RLP.encode(o -> o.writeList(receipts, TransactionReceipt::writeTo));
return RLP.encode(o -> o.writeList(receipts, TransactionReceipt::writeToWithRevertReason));
}
}
}

@ -298,7 +298,13 @@ public class BlockDataGenerator {
}
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() {

@ -113,7 +113,8 @@ public class ExecutionContextTestFixture {
new StubGenesisConfigOptions().istanbulBlock(0),
BigInteger.valueOf(42),
Function.identity(),
new PrivacyParameters())
new PrivacyParameters(),
false)
.createProtocolSchedule();
}
if (keyValueStorage == null) {

@ -25,7 +25,16 @@ public class TransactionReceiptTest {
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);
}
}

@ -17,7 +17,6 @@ import static org.assertj.core.api.Assertions.assertThat;
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<Void> schedule =
FixedDifficultyProtocolSchedule.create(
GenesisConfigFile.development().getConfigOptions(), PrivacyParameters.DEFAULT);
FixedDifficultyProtocolSchedule.create(GenesisConfigFile.development().getConfigOptions());
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();

@ -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 class MainnetProtocolScheduleTest {
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");

@ -67,7 +67,7 @@ public class ReferenceTestProtocolSchedules {
private static ProtocolSchedule<Void> createSchedule(final GenesisConfigOptions options) {
return new ProtocolScheduleBuilder<>(
options, CHAIN_ID, Function.identity(), PrivacyParameters.DEFAULT)
options, CHAIN_ID, Function.identity(), PrivacyParameters.DEFAULT, false)
.createProtocolSchedule();
}
}

@ -67,7 +67,7 @@ public final class ReceiptsMessage extends AbstractMessageData {
final int setSize = input.enterList();
final List<TransactionReceipt> 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);

@ -66,7 +66,7 @@ public final class BlockBodiesMessageTest {
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));

@ -58,7 +58,7 @@ public final class BlockHeadersMessageTest {
final List<BlockHeader> 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));

@ -44,7 +44,8 @@ public class ChainHeadTrackerTest {
blockchain.getChainHead().getTotalDifficulty(),
0);
private final ProtocolSchedule<Void> protocolSchedule =
FixedDifficultyProtocolSchedule.create(GenesisConfigFile.development().getConfigOptions());
FixedDifficultyProtocolSchedule.create(
GenesisConfigFile.development().getConfigOptions(), false);
private final TrailingPeerLimiter trailingPeerLimiter = mock(TrailingPeerLimiter.class);
private final ChainHeadTracker chainHeadTracker =

@ -94,7 +94,8 @@ public class TestNode implements Closeable {
final GenesisConfigFile genesisConfigFile = GenesisConfigFile.development();
final ProtocolSchedule<Void> protocolSchedule =
FixedDifficultyProtocolSchedule.create(GenesisConfigFile.development().getConfigOptions());
FixedDifficultyProtocolSchedule.create(
GenesisConfigFile.development().getConfigOptions(), false);
final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, protocolSchedule);
final BlockHeaderFunctions blockHeaderFunctions =

@ -66,6 +66,7 @@ import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.junit.Before;
@ -638,7 +639,7 @@ public class TransactionPoolTest {
new BlockBody(transactionList, emptyList()));
final List<TransactionReceipt> 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;

@ -58,6 +58,7 @@ import tech.pegasys.pantheon.util.uint.UInt256;
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 @@ public class EthGetFilterChangesIntegrationTest {
new BlockBody(transactionList, emptyList()));
final List<TransactionReceipt> 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;

@ -23,6 +23,7 @@ import java.util.ArrayList;
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 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
"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 abstract class TransactionReceiptResult {
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 abstract class TransactionReceiptResult {
return transactionIndex;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonGetter(value = "revertReason")
public String getRevertReason() {
return revertReason;
}
private List<TransactionReceiptLogResult> logReceipts(
final List<Log> logs,
final long blockNumber,

@ -44,11 +44,11 @@ import org.junit.Test;
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 =

@ -195,7 +195,7 @@ public class LogsSubscriptionServiceTest {
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,

@ -552,6 +552,12 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
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 class PantheonCommand implements DefaultCommandValues, Runnable {
.metricsSystem(metricsSystem.get())
.privacyParameters(privacyParameters())
.clock(Clock.systemUTC())
.isRevertReasonEnabled(isRevertReasonEnabled)
.build();
} catch (final InvalidConfigurationException e) {
throw new ExecutionException(this.commandLine, e.getMessage());

@ -121,7 +121,7 @@ public class CliquePantheonControllerBuilder extends PantheonControllerBuilder<C
@Override
protected ProtocolSchedule<CliqueContext> createProtocolSchedule() {
return CliqueProtocolSchedule.create(
genesisConfig.getConfigOptions(), nodeKeys, privacyParameters);
genesisConfig.getConfigOptions(), nodeKeys, privacyParameters, isRevertReasonEnabled);
}
@Override

@ -63,7 +63,8 @@ public class IbftLegacyPantheonControllerBuilder extends PantheonControllerBuild
@Override
protected ProtocolSchedule<IbftContext> createProtocolSchedule() {
return IbftProtocolSchedule.create(genesisConfig.getConfigOptions(), privacyParameters);
return IbftProtocolSchedule.create(
genesisConfig.getConfigOptions(), privacyParameters, isRevertReasonEnabled);
}
@Override

@ -210,7 +210,8 @@ public class IbftPantheonControllerBuilder extends PantheonControllerBuilder<Ibf
@Override
protected ProtocolSchedule<IbftContext> createProtocolSchedule() {
return IbftProtocolSchedule.create(genesisConfig.getConfigOptions(), privacyParameters);
return IbftProtocolSchedule.create(
genesisConfig.getConfigOptions(), privacyParameters, isRevertReasonEnabled);
}
@Override

@ -85,6 +85,7 @@ public class MainnetPantheonControllerBuilder extends PantheonControllerBuilder<
@Override
protected ProtocolSchedule<Void> createProtocolSchedule() {
return MainnetProtocolSchedule.fromConfig(genesisConfig.getConfigOptions(), privacyParameters);
return MainnetProtocolSchedule.fromConfig(
genesisConfig.getConfigOptions(), privacyParameters, isRevertReasonEnabled);
}
}

@ -75,6 +75,7 @@ public abstract class PantheonControllerBuilder<C> {
protected Path dataDirectory;
protected Clock clock;
protected KeyPair nodeKeys;
protected boolean isRevertReasonEnabled;
private StorageProvider storageProvider;
private final List<Runnable> shutdownActions = new ArrayList<>();
private RocksDbConfiguration rocksDbConfiguration;
@ -154,6 +155,11 @@ public abstract class PantheonControllerBuilder<C> {
return this;
}
public PantheonControllerBuilder<C> isRevertReasonEnabled(final boolean isRevertReasonEnabled) {
this.isRevertReasonEnabled = isRevertReasonEnabled;
return this;
}
public PantheonController<C> build() throws IOException {
checkNotNull(genesisConfig, "Missing genesis config");
checkNotNull(syncConfig, "Missing sync config");

@ -141,6 +141,7 @@ public abstract class CommandTestAbstract {
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();

@ -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
Xincoming-tx-messages-keep-alive-seconds=60
# Revert Reason
revert-reason-enabled=false
Loading…
Cancel
Save