Tracing private transactions feature (#6161)

* add private tx tracing feature

Signed-off-by: Nischal Sharma <nischal@web3labs.com>
Signed-off-by: Stefan Pingel <16143240+pinges@users.noreply.github.com>
Co-authored-by: Stefan Pingel <16143240+pinges@users.noreply.github.com>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
pull/7454/head
Nischal Sharma 3 months ago committed by GitHub
parent 290f21c227
commit 50f8add052
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 7
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java
  3. 49
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivTraceTransaction.java
  4. 12
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java
  5. 174
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivTraceTransactionAcceptanceTest.java
  6. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java
  7. 167
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/AbstractPrivateTraceByHash.java
  8. 93
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivTraceTransaction.java
  9. 110
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockReplay.java
  10. 30
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockTrace.java
  11. 87
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockTracer.java
  12. 118
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateTracer.java
  13. 91
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateTransactionTrace.java
  14. 377
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/privacy/privateTracing/PrivateFlatTrace.java
  15. 598
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/privacy/privateTracing/PrivateTraceGenerator.java
  16. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/Action.java
  17. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/FlatTraceGenerator.java
  18. 16
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java
  19. 6
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/PrivacyQueries.java
  20. 1
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethodsTest.java
  21. 10
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java
  22. 6
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReader.java
  23. 2
      plugin-api/build.gradle
  24. 10
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/PrivateTransaction.java

@ -15,8 +15,10 @@
### Additions and Improvements ### Additions and Improvements
- Expose set finalized/safe block in plugin api BlockchainService. These method can be used by plugins to set finalized/safe block for a PoA network (such as QBFT, IBFT and Clique).[#7382](https://github.com/hyperledger/besu/pull/7382) - Expose set finalized/safe block in plugin api BlockchainService. These method can be used by plugins to set finalized/safe block for a PoA network (such as QBFT, IBFT and Clique).[#7382](https://github.com/hyperledger/besu/pull/7382)
- In process RPC service [#7395](https://github.com/hyperledger/besu/pull/7395) - In process RPC service [#7395](https://github.com/hyperledger/besu/pull/7395)
- Added support for tracing private transactions using `priv_traceTransaction` API. [#6161](https://github.com/hyperledger/besu/pull/6161)
- Wrap WorldUpdater into EVMWorldupdater [#7434](https://github.com/hyperledger/besu/pull/7434) - Wrap WorldUpdater into EVMWorldupdater [#7434](https://github.com/hyperledger/besu/pull/7434)
### Bug fixes ### Bug fixes
- Correct entrypoint in Docker evmtool [#7430](https://github.com/hyperledger/besu/pull/7430) - Correct entrypoint in Docker evmtool [#7430](https://github.com/hyperledger/besu/pull/7430)
- Fix protocol schedule check for devnets [#7429](https://github.com/hyperledger/besu/pull/7429) - Fix protocol schedule check for devnets [#7429](https://github.com/hyperledger/besu/pull/7429)

@ -15,6 +15,7 @@
package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction;
import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil; import org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil;
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode;
import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.PrivGetTransactionReceiptTransaction; import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.PrivGetTransactionReceiptTransaction;
@ -25,6 +26,7 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivDebugGe
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetCodeTransaction; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetCodeTransaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetLogsTransaction; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetLogsTransaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetTransaction; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetTransaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivTraceTransaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivGetFilterChangesTransaction; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivGetFilterChangesTransaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivGetFilterLogsTransaction; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivGetFilterLogsTransaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivNewFilterTransaction; import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivNewFilterTransaction;
@ -115,6 +117,11 @@ public class PrivacyTransactions {
return new PrivGetLogsTransaction(privacyGroupId, filterParameter); return new PrivGetLogsTransaction(privacyGroupId, filterParameter);
} }
public PrivTraceTransaction privTraceTransaction(
final String privacyGroupId, final Hash transactionHash) {
return new PrivTraceTransaction(privacyGroupId, transactionHash);
}
public RemoveFromFlexiblePrivacyGroupTransaction removeFromPrivacyGroup( public RemoveFromFlexiblePrivacyGroupTransaction removeFromPrivacyGroup(
final String privacyGroupId, final String privacyGroupId,
final String remover, final String remover,

@ -0,0 +1,49 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
public class PrivTraceTransaction implements Transaction<String> {
private final String privacyGroupId;
private final Hash transactionHash;
public PrivTraceTransaction(final String privacyGroupId, final Hash transactionHash) {
this.privacyGroupId = privacyGroupId;
this.transactionHash = transactionHash;
}
@Override
public String execute(final NodeRequests node) {
try {
final PrivacyRequestFactory.PrivTraceTransaction response =
node.privacy().privTraceTransaction(privacyGroupId, transactionHash).send();
assertThat(response).as("check response is not null").isNotNull();
assertThat(response.getResult()).as("check result in response is not null").isNotNull();
return response.getResult();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -109,6 +109,8 @@ public class PrivacyRequestFactory {
public static class GetPrivateTransactionResponse public static class GetPrivateTransactionResponse
extends Response<PrivateTransactionGroupResponse> {} extends Response<PrivateTransactionGroupResponse> {}
public static class PrivTraceTransaction extends Response<String> {}
public static class CreatePrivacyGroupResponse extends Response<String> {} public static class CreatePrivacyGroupResponse extends Response<String> {}
public static class DeletePrivacyGroupResponse extends Response<String> {} public static class DeletePrivacyGroupResponse extends Response<String> {}
@ -455,6 +457,16 @@ public class PrivacyRequestFactory {
"priv_getLogs", Arrays.asList(privacyGroupId, filterParameter), web3jService, EthLog.class); "priv_getLogs", Arrays.asList(privacyGroupId, filterParameter), web3jService, EthLog.class);
} }
public Request<?, PrivTraceTransaction> privTraceTransaction(
final String privacyGroupId, final Hash transactionHash) {
return new Request<>(
"priv_traceTransaction",
Arrays.asList(privacyGroupId, transactionHash),
web3jService,
PrivTraceTransaction.class);
}
public Request<?, EthFilter> privNewFilter( public Request<?, EthFilter> privNewFilter(
final String privacyGroupId, final LogFilterJsonParameter filterParameter) { final String privacyGroupId, final LogFilterJsonParameter filterParameter) {
return new Request<>( return new Request<>(

@ -0,0 +1,174 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.tests.acceptance.privacy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.web3j.utils.Restriction.UNRESTRICTED;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode;
import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver;
import org.hyperledger.besu.tests.web3j.generated.SimpleStorage;
import org.hyperledger.enclave.testutil.EnclaveEncryptorType;
import org.hyperledger.enclave.testutil.EnclaveType;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.web3j.utils.Restriction;
public class PrivTraceTransactionAcceptanceTest extends ParameterizedEnclaveTestBase {
private final PrivacyNode node;
private final PrivacyNode wrongNode;
public PrivTraceTransactionAcceptanceTest(
final Restriction restriction,
final EnclaveType enclaveType,
final EnclaveEncryptorType enclaveEncryptorType)
throws IOException {
super(restriction, enclaveType, enclaveEncryptorType);
node =
privacyBesu.createPrivateTransactionEnabledMinerNode(
restriction + "-node",
PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType),
enclaveType,
Optional.empty(),
false,
false,
restriction == UNRESTRICTED);
wrongNode =
privacyBesu.createPrivateTransactionEnabledMinerNode(
restriction + "-node",
PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType),
enclaveType,
Optional.empty(),
false,
false,
restriction == UNRESTRICTED);
privacyCluster.start(node);
privacyCluster.start(wrongNode);
}
@Test
public void getTransactionTrace() throws JsonProcessingException {
final String privacyGroupId = createPrivacyGroup();
final SimpleStorage simpleStorageContract = deploySimpleStorageContract(privacyGroupId);
Hash transactionHash =
Hash.fromHexString(doTransaction(privacyGroupId, simpleStorageContract, 0));
final String result =
node.execute(privacyTransactions.privTraceTransaction(privacyGroupId, transactionHash));
assertThat(result).isNotNull();
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(result);
JsonNode resultNode = rootNode.get("result");
assertThat(resultNode).isNotNull();
assertThat(resultNode.isArray()).isTrue();
assertThat(resultNode.size()).isGreaterThan(0);
JsonNode trace = resultNode.get(0);
assertThat(trace.get("action").get("callType").asText()).isEqualTo("call");
assertThat(trace.get("action").get("from").asText()).isEqualTo(node.getAddress().toString());
assertThat(trace.get("action").get("input").asText()).startsWith("0x60fe47b1");
assertThat(trace.get("action").get("to").asText())
.isEqualTo(simpleStorageContract.getContractAddress());
assertThat(trace.get("action").get("value").asText()).isEqualTo("0x0");
assertThat(trace.get("blockHash").asText()).isNotEmpty();
assertThat(trace.get("blockNumber").asInt()).isGreaterThan(0);
assertThat(trace.get("transactionHash").asText()).isEqualTo(transactionHash.toString());
assertThat(trace.get("type").asText()).isEqualTo("call");
final String wrongPrivacyGroupId = createWrongPrivacyGroup();
final String resultEmpty =
wrongNode.execute(
privacyTransactions.privTraceTransaction(wrongPrivacyGroupId, transactionHash));
ObjectMapper mapperEmpty = new ObjectMapper();
JsonNode rootNodeEmpty = mapperEmpty.readTree(resultEmpty);
JsonNode resultNodeEmpty = rootNodeEmpty.get("result");
assertThat(resultNodeEmpty).isNotNull();
assertThat(resultNodeEmpty.isArray()).isTrue();
assertThat(resultNodeEmpty.isEmpty()).isTrue();
final String resultWrongHash =
wrongNode.execute(privacyTransactions.privTraceTransaction(privacyGroupId, Hash.EMPTY));
ObjectMapper mapperWrongHash = new ObjectMapper();
JsonNode rootNodeWrongHash = mapperWrongHash.readTree(resultWrongHash);
JsonNode resultNodeWrongHash = rootNodeWrongHash.get("result");
assertThat(resultNodeWrongHash).isNotNull();
assertThat(resultNodeWrongHash.isArray()).isTrue();
assertThat(resultNodeWrongHash.isEmpty()).isTrue();
}
private String createPrivacyGroup() {
return node.execute(createPrivacyGroup("myGroupName", "my group description", node));
}
private String createWrongPrivacyGroup() {
return wrongNode.execute(createPrivacyGroup("myGroupName", "my group description", wrongNode));
}
private SimpleStorage deploySimpleStorageContract(final String privacyGroupId) {
final SimpleStorage simpleStorage =
node.execute(
privateContractTransactions.createSmartContractWithPrivacyGroupId(
SimpleStorage.class,
node.getTransactionSigningKey(),
restriction,
node.getEnclaveKey(),
privacyGroupId));
privateContractVerifier
.validPrivateContractDeployed(
simpleStorage.getContractAddress(), node.getAddress().toString())
.verify(simpleStorage);
return simpleStorage;
}
private String doTransaction(
final String privacyGroupId, final SimpleStorage simpleStorageContract, final int value) {
return node.execute(
privateContractTransactions.callSmartContractWithPrivacyGroupId(
simpleStorageContract.getContractAddress(),
simpleStorageContract.set(BigInteger.valueOf(value)).encodeFunctionCall(),
node.getTransactionSigningKey(),
restriction,
node.getEnclaveKey(),
privacyGroupId));
}
}

@ -86,6 +86,7 @@ public enum RpcMethod {
PRIV_GET_FILTER_LOGS("priv_getFilterLogs"), PRIV_GET_FILTER_LOGS("priv_getFilterLogs"),
PRIV_SUBSCRIBE("priv_subscribe"), PRIV_SUBSCRIBE("priv_subscribe"),
PRIV_UNSUBSCRIBE("priv_unsubscribe"), PRIV_UNSUBSCRIBE("priv_unsubscribe"),
PRIV_TRACE_TRANSACTION("priv_traceTransaction"),
PRIVX_FIND_PRIVACY_GROUP_OLD("privx_findOnchainPrivacyGroup"), PRIVX_FIND_PRIVACY_GROUP_OLD("privx_findOnchainPrivacyGroup"),
PRIVX_FIND_PRIVACY_GROUP("privx_findFlexiblePrivacyGroup"), PRIVX_FIND_PRIVACY_GROUP("privx_findFlexiblePrivacyGroup"),
EEA_SEND_RAW_TRANSACTION("eea_sendRawTransaction"), EEA_SEND_RAW_TRANSACTION("eea_sendRawTransaction"),

@ -0,0 +1,167 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateTransactionTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing.PrivateFlatTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing.PrivateTraceGenerator;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.PrivacyQueries;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.debug.TraceOptions;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.privacy.ExecutedPrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public abstract class AbstractPrivateTraceByHash implements JsonRpcMethod {
protected final Supplier<PrivateBlockTracer> blockTracerSupplier;
protected final BlockchainQueries blockchainQueries;
protected final PrivacyQueries privacyQueries;
protected final ProtocolSchedule protocolSchedule;
protected final PrivacyController privacyController;
protected final PrivacyParameters privacyParameters;
protected final PrivacyIdProvider privacyIdProvider;
protected AbstractPrivateTraceByHash(
final Supplier<PrivateBlockTracer> blockTracerSupplier,
final BlockchainQueries blockchainQueries,
final PrivacyQueries privacyQueries,
final ProtocolSchedule protocolSchedule,
final PrivacyController privacyController,
final PrivacyParameters privacyParameters,
final PrivacyIdProvider privacyIdProvider) {
this.blockTracerSupplier = blockTracerSupplier;
this.blockchainQueries = blockchainQueries;
this.privacyQueries = privacyQueries;
this.protocolSchedule = protocolSchedule;
this.privacyController = privacyController;
this.privacyParameters = privacyParameters;
this.privacyIdProvider = privacyIdProvider;
}
public Stream<PrivateFlatTrace> resultByTransactionHash(
final String privacyGroupId,
final Hash transactionHash,
final JsonRpcRequestContext requestContext) {
final String enclaveKey = privacyIdProvider.getPrivacyUserId(requestContext.getUser());
if (privacyController instanceof MultiTenancyPrivacyController) {
verifyPrivacyGroupMatchesAuthenticatedEnclaveKey(
requestContext, privacyGroupId, Optional.empty());
}
return privacyController
.findPrivateTransactionByPmtHash(transactionHash, enclaveKey)
.map(ExecutedPrivateTransaction::getBlockNumber)
.flatMap(blockNumber -> blockchainQueries.getBlockchain().getBlockHashByNumber(blockNumber))
.map(blockHash -> getTraceBlock(blockHash, transactionHash, enclaveKey, privacyGroupId))
.orElse(Stream.empty());
}
private Stream<PrivateFlatTrace> getTraceBlock(
final Hash blockHash,
final Hash transactionHash,
final String enclaveKey,
final String privacyGroupId) {
final Block block = blockchainQueries.getBlockchain().getBlockByHash(blockHash).orElse(null);
final PrivateBlockMetadata privateBlockMetadata =
privacyQueries.getPrivateBlockMetaData(privacyGroupId, blockHash).orElse(null);
if (privateBlockMetadata == null || block == null) {
return Stream.empty();
}
return PrivateTracer.processTracing(
blockchainQueries,
Optional.of(block.getHeader()),
privacyGroupId,
enclaveKey,
privacyParameters,
privacyController,
mutableWorldState -> {
final PrivateTransactionTrace privateTransactionTrace =
getTransactionTrace(
block, transactionHash, enclaveKey, privateBlockMetadata, privacyGroupId);
return Optional.ofNullable(getTraceStream(privateTransactionTrace, block));
})
.orElse(Stream.empty());
}
private PrivateTransactionTrace getTransactionTrace(
final Block block,
final Hash transactionHash,
final String enclaveKey,
final PrivateBlockMetadata privateBlockMetadata,
final String privacyGroupId) {
return PrivateTracer.processTracing(
blockchainQueries,
Optional.of(block.getHeader()),
privacyGroupId,
enclaveKey,
privacyParameters,
privacyController,
mutableWorldState ->
blockTracerSupplier
.get()
.trace(
mutableWorldState,
block,
new DebugOperationTracer(new TraceOptions(false, false, true), false),
enclaveKey,
privacyGroupId,
privateBlockMetadata)
.map(PrivateBlockTrace::getTransactionTraces)
.orElse(Collections.emptyList())
.stream()
.filter(
trxTrace ->
trxTrace.getPrivateTransaction().getPmtHash().equals(transactionHash))
.findFirst())
.orElseThrow();
}
private Stream<PrivateFlatTrace> getTraceStream(
final PrivateTransactionTrace transactionTrace, final Block block) {
return PrivateTraceGenerator.generateFromTransactionTraceAndBlock(
this.protocolSchedule, transactionTrace, block)
.map(PrivateFlatTrace.class::cast);
}
private void verifyPrivacyGroupMatchesAuthenticatedEnclaveKey(
final JsonRpcRequestContext request,
final String privacyGroupId,
final Optional<Long> toBlock) {
final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser());
privacyController.verifyPrivacyGroupContainsPrivacyUserId(
privacyGroupId, privacyUserId, toBlock);
}
}

@ -0,0 +1,93 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.TraceTransaction;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing.PrivateFlatTrace;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.PrivacyQueries;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrivTraceTransaction extends AbstractPrivateTraceByHash implements JsonRpcMethod {
private static final Logger LOG = LoggerFactory.getLogger(TraceTransaction.class);
public PrivTraceTransaction(
final Supplier<PrivateBlockTracer> blockTracerSupplier,
final BlockchainQueries blockchainQueries,
final ProtocolSchedule protocolSchedule,
final PrivacyQueries privacyQueries,
final PrivacyController privacyController,
final PrivacyParameters privacyParameters,
final PrivacyIdProvider privacyIdProvider) {
super(
blockTracerSupplier,
blockchainQueries,
privacyQueries,
protocolSchedule,
privacyController,
privacyParameters,
privacyIdProvider);
}
@Override
public String getName() {
return RpcMethod.PRIV_TRACE_TRANSACTION.getMethodName();
}
@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
final String privacyGroupId = requestContext.getRequiredParameter(0, String.class);
final Hash transactionHash = requestContext.getRequiredParameter(1, Hash.class);
LOG.trace("Received RPC rpcName={} txHash={}", getName(), transactionHash);
if (privacyGroupId.isEmpty() || transactionHash.isEmpty()) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS);
}
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(),
arrayNodeFromTraceStream(
resultByTransactionHash(privacyGroupId, transactionHash, requestContext)));
}
protected JsonNode arrayNodeFromTraceStream(final Stream<PrivateFlatTrace> traceStream) {
final ObjectMapper mapper = new ObjectMapper();
final ArrayNode resultArrayNode = mapper.createArrayNode();
traceStream.forEachOrdered(resultArrayNode::addPOJO);
return resultArrayNode;
}
}

@ -0,0 +1,110 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.privacy.ExecutedPrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import java.util.List;
import java.util.Optional;
public class PrivateBlockReplay {
private final ProtocolSchedule protocolSchedule;
private final Blockchain blockchain;
private final PrivacyController privacyController;
public PrivateBlockReplay(
final ProtocolSchedule protocolSchedule,
final Blockchain blockchain,
final PrivacyController privacyController) {
this.protocolSchedule = protocolSchedule;
this.blockchain = blockchain;
this.privacyController = privacyController;
}
public Optional<PrivateBlockTrace> block(
final Block block,
final PrivateBlockMetadata privateBlockMetadata,
final String enclaveKey,
final TransactionAction<PrivateTransactionTrace> action) {
return performActionWithBlock(
block.getHeader(),
block.getBody(),
(body, header, blockchain, transactionProcessor, protocolSpec) -> {
final List<PrivateTransactionTrace> transactionTraces =
privateBlockMetadata.getPrivateTransactionMetadataList().stream()
.map(
privateTransactionMetadata ->
privacyController
.findPrivateTransactionByPmtHash(
privateTransactionMetadata.getPrivateMarkerTransactionHash(),
enclaveKey)
.map(
executedPrivateTransaction ->
action.performAction(
executedPrivateTransaction,
header,
blockchain,
transactionProcessor))
.orElse(null))
.toList();
return Optional.of(new PrivateBlockTrace(transactionTraces));
});
}
private <T> Optional<T> performActionWithBlock(
final BlockHeader header, final BlockBody body, final BlockAction<T> action) {
if (header == null) {
return Optional.empty();
}
if (body == null) {
return Optional.empty();
}
final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(header);
final PrivateTransactionProcessor transactionProcessor =
protocolSpec.getPrivateTransactionProcessor();
return action.perform(body, header, blockchain, transactionProcessor, protocolSpec);
}
@FunctionalInterface
public interface BlockAction<T> {
Optional<T> perform(
BlockBody body,
BlockHeader blockHeader,
Blockchain blockchain,
PrivateTransactionProcessor transactionProcessor,
ProtocolSpec protocolSpec);
}
@FunctionalInterface
public interface TransactionAction<T> {
T performAction(
ExecutedPrivateTransaction transaction,
BlockHeader blockHeader,
Blockchain blockchain,
PrivateTransactionProcessor transactionProcessor);
}
}

@ -0,0 +1,30 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor;
import java.util.List;
public class PrivateBlockTrace {
private final List<PrivateTransactionTrace> transactionTraces;
public PrivateBlockTrace(final List<PrivateTransactionTrace> transactionTraces) {
this.transactionTraces = transactionTraces;
}
public List<PrivateTransactionTrace> getTransactionTraces() {
return transactionTraces;
}
}

@ -0,0 +1,87 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.evm.worldstate.StackedUpdater;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
public class PrivateBlockTracer {
private final PrivateBlockReplay blockReplay;
// Either the initial block state or the state of the prior TX, including miner rewards.
private WorldUpdater chainedUpdater;
public PrivateBlockTracer(final PrivateBlockReplay blockReplay) {
this.blockReplay = blockReplay;
}
public Optional<PrivateBlockTrace> trace(
final PrivateTracer.TraceableState mutableWorldState,
final Block block,
final DebugOperationTracer tracer,
final String enclaveKey,
final String privacyGroupId,
final PrivateBlockMetadata privateBlockMetadata) {
return blockReplay.block(
block,
privateBlockMetadata,
enclaveKey,
prepareReplayAction(mutableWorldState, tracer, privacyGroupId));
}
private PrivateBlockReplay.TransactionAction<PrivateTransactionTrace> prepareReplayAction(
final PrivateTracer.TraceableState mutableWorldState,
final DebugOperationTracer tracer,
final String privacyGroupId) {
return (transaction, header, blockchain, transactionProcessor) -> {
// if we have no prior updater, it must be the first TX, so use the block's initial state
if (chainedUpdater == null) {
chainedUpdater = mutableWorldState.updater();
} else if (chainedUpdater instanceof StackedUpdater<?, ?> stackedUpdater) {
stackedUpdater.markTransactionBoundary();
}
// create an updater for just this tx
chainedUpdater = chainedUpdater.updater();
WorldUpdater privateChainedUpdater = mutableWorldState.privateUpdater();
final TransactionProcessingResult result =
transactionProcessor.processTransaction(
chainedUpdater,
privateChainedUpdater,
header,
transaction.getPmtHash(),
transaction,
header.getCoinbase(),
tracer,
new CachingBlockHashLookup(header, blockchain),
Bytes32.wrap(Bytes.fromBase64String(privacyGroupId)));
final List<TraceFrame> traceFrames = tracer.copyTraceFrames();
tracer.reset();
return new PrivateTransactionTrace(transaction, result, traceFrames);
};
}
}

@ -0,0 +1,118 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.tuweni.bytes.Bytes32;
public class PrivateTracer {
public static <TRACE> Optional<TRACE> processTracing(
final BlockchainQueries blockchainQueries,
final Optional<BlockHeader> blockHeader,
final String privacyGroupId,
final String enclaveKey,
final PrivacyParameters privacyParameters,
final PrivacyController privacyController,
final Function<PrivateTracer.TraceableState, ? extends Optional<TRACE>> mapper) {
return blockHeader.flatMap(
header -> {
final long blockNumber = header.getNumber();
final Hash parentHash = header.getParentHash();
final MutableWorldState disposablePrivateState =
privacyParameters
.getPrivateWorldStateArchive()
.getMutable(
privacyController
.getStateRootByBlockNumber(privacyGroupId, enclaveKey, blockNumber)
.get(),
parentHash)
.get();
return blockchainQueries.getAndMapWorldState(
parentHash,
mutableWorldState ->
mapper.apply(
new PrivateTracer.TraceableState(mutableWorldState, disposablePrivateState)));
});
}
/**
* This class force the use of the processTracing method to do tracing. processTracing allows you
* to cleanly manage the worldstate, to close it etc
*/
public static class TraceableState implements MutableWorldState {
private final MutableWorldState mutableWorldState;
private final MutableWorldState disposableWorldState;
private TraceableState(
final MutableWorldState mutableWorldState, final MutableWorldState disposableWorldState) {
this.mutableWorldState = mutableWorldState;
this.disposableWorldState = disposableWorldState;
}
@Override
public WorldUpdater updater() {
return mutableWorldState.updater();
}
public WorldUpdater privateUpdater() {
return disposableWorldState.updater();
}
@Override
public Hash rootHash() {
return mutableWorldState.rootHash();
}
@Override
public Hash frontierRootHash() {
return mutableWorldState.rootHash();
}
@Override
public Stream<StreamableAccount> streamAccounts(final Bytes32 startKeyHash, final int limit) {
return mutableWorldState.streamAccounts(startKeyHash, limit);
}
@Override
public Account get(final Address address) {
return mutableWorldState.get(address);
}
@Override
public void close() throws Exception {
mutableWorldState.close();
}
@Override
public void persist(final BlockHeader blockHeader) {}
}
}

@ -0,0 +1,91 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import org.hyperledger.besu.ethereum.privacy.ExecutedPrivateTransaction;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import java.util.List;
import java.util.Optional;
public class PrivateTransactionTrace {
private final ExecutedPrivateTransaction privateTransaction;
private final TransactionProcessingResult result;
private final List<TraceFrame> traceFrames;
private final Optional<Block> block;
public PrivateTransactionTrace(final Optional<Block> block) {
this.privateTransaction = null;
this.result = null;
this.traceFrames = null;
this.block = block;
}
public PrivateTransactionTrace(
final ExecutedPrivateTransaction privateTransaction,
final TransactionProcessingResult result,
final List<TraceFrame> traceFrames) {
this.privateTransaction = privateTransaction;
this.result = result;
this.traceFrames = traceFrames;
this.block = Optional.empty();
}
public PrivateTransactionTrace(
final ExecutedPrivateTransaction privateTransaction,
final TransactionProcessingResult result,
final List<TraceFrame> traceFrames,
final Optional<Block> block) {
this.privateTransaction = privateTransaction;
this.result = result;
this.traceFrames = traceFrames;
this.block = block;
}
public PrivateTransactionTrace(
final ExecutedPrivateTransaction privateTransaction, final Optional<Block> block) {
this.privateTransaction = privateTransaction;
this.result = null;
this.traceFrames = null;
this.block = block;
}
public ExecutedPrivateTransaction getPrivateTransaction() {
return privateTransaction;
}
public long getGas() {
return privateTransaction.getGasLimit() - result.getGasRemaining();
}
public long getGasLimit() {
return privateTransaction.getGasLimit();
}
public TransactionProcessingResult getResult() {
return result;
}
public List<TraceFrame> getTraceFrames() {
return traceFrames;
}
public Optional<Block> getBlock() {
return block;
}
}

@ -0,0 +1,377 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateTransactionTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.Trace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.Action;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.Result;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({
"action",
"blockHash",
"blockNumber",
"result",
"error",
"revertReason",
"subtraces",
"traceAddress",
"transactionHash",
"transactionPosition",
"type"
})
public class PrivateFlatTrace implements Trace {
private final Action action;
private final Result result;
private final Long blockNumber;
private final String blockHash;
private final Integer transactionPosition;
private final String transactionHash;
private final Optional<String> error;
private final String revertReason;
private final int subtraces;
private final List<Integer> traceAddress;
private final String type;
protected PrivateFlatTrace(
final Action.Builder actionBuilder,
final Result.Builder resultBuilder,
final int subtraces,
final List<Integer> traceAddress,
final String type,
final Long blockNumber,
final String blockHash,
final Integer transactionPosition,
final String transactionHash,
final Optional<String> error) {
this(
actionBuilder != null ? actionBuilder.build() : null,
resultBuilder != null ? resultBuilder.build() : null,
subtraces,
traceAddress,
type,
blockNumber,
blockHash,
transactionPosition,
transactionHash,
error,
null);
}
protected PrivateFlatTrace(
final Action.Builder actionBuilder,
final Result.Builder resultBuilder,
final int subtraces,
final List<Integer> traceAddress,
final String type,
final Long blockNumber,
final String blockHash,
final Integer transactionPosition,
final String transactionHash,
final Optional<String> error,
final String revertReason) {
this(
actionBuilder != null ? actionBuilder.build() : null,
resultBuilder != null ? resultBuilder.build() : null,
subtraces,
traceAddress,
type,
blockNumber,
blockHash,
transactionPosition,
transactionHash,
error,
revertReason);
}
protected PrivateFlatTrace(
final Action action,
final Result result,
final int subtraces,
final List<Integer> traceAddress,
final String type,
final Long blockNumber,
final String blockHash,
final Integer transactionPosition,
final String transactionHash,
final Optional<String> error,
final String revertReason) {
this.action = action;
this.result = result;
this.subtraces = subtraces;
this.traceAddress = traceAddress;
this.type = type;
this.blockNumber = blockNumber;
this.blockHash = blockHash;
this.transactionPosition = transactionPosition;
this.transactionHash = transactionHash;
this.error = error;
this.revertReason = revertReason;
}
static PrivateFlatTrace.Builder freshBuilder(final PrivateTransactionTrace transactionTrace) {
return PrivateFlatTrace.builder()
.resultBuilder(Result.builder())
.actionBuilder(from(transactionTrace));
}
public static Action.Builder from(final PrivateTransactionTrace trace) {
final Action.Builder builder =
Action.builder()
.from(trace.getPrivateTransaction().getSender().toHexString())
.value(Quantity.create(trace.getPrivateTransaction().getValue()));
if (!trace.getTraceFrames().isEmpty()) {
final TraceFrame traceFrame = trace.getTraceFrames().get(0);
builder.gas(
"0x"
+ Long.toHexString(
traceFrame.getGasRemaining() + (traceFrame.getPrecompiledGasCost().orElse(0L))));
}
return builder;
}
public Action getAction() {
return action;
}
@JsonInclude(NON_NULL)
public Long getBlockNumber() {
return blockNumber;
}
@JsonInclude(NON_NULL)
public String getBlockHash() {
return blockHash;
}
@JsonInclude(NON_NULL)
public String getTransactionHash() {
return transactionHash;
}
@JsonInclude(NON_NULL)
public Integer getTransactionPosition() {
return transactionPosition;
}
@JsonInclude(NON_NULL)
public String getError() {
return error.orElse(null);
}
@JsonInclude(NON_NULL)
public String getRevertReason() {
return revertReason;
}
@JsonInclude(NON_NULL)
public AtomicReference<Result> getResult() {
return (error.isPresent()) ? null : new AtomicReference<>(result);
}
public int getSubtraces() {
return subtraces;
}
public List<Integer> getTraceAddress() {
return traceAddress;
}
public String getType() {
return type;
}
public static PrivateFlatTrace.Builder builder() {
return new PrivateFlatTrace.Builder();
}
public static class Context {
private final PrivateFlatTrace.Builder builder;
private long gasUsed = 0;
private boolean createOp;
Context(final PrivateFlatTrace.Builder builder) {
this.builder = builder;
}
public PrivateFlatTrace.Builder getBuilder() {
return builder;
}
void incGasUsed(final long gas) {
setGasUsed(gasUsed + gas);
}
void decGasUsed(final long gas) {
setGasUsed(gasUsed - gas);
}
public long getGasUsed() {
return gasUsed;
}
public void setGasUsed(final long gasUsed) {
this.gasUsed = gasUsed;
builder.getResultBuilder().gasUsed("0x" + Long.toHexString(gasUsed));
}
boolean isCreateOp() {
return createOp;
}
void setCreateOp(final boolean createOp) {
this.createOp = createOp;
}
}
public static class Builder {
private Action.Builder actionBuilder;
private Result.Builder resultBuilder;
private int subtraces;
private List<Integer> traceAddress = new ArrayList<>();
private String type = "call";
private Long blockNumber;
private String blockHash;
private String transactionHash;
private Integer transactionPosition;
private Optional<String> error = Optional.empty();
private String revertReason;
protected Builder() {}
PrivateFlatTrace.Builder resultBuilder(final Result.Builder resultBuilder) {
this.resultBuilder = resultBuilder;
return this;
}
PrivateFlatTrace.Builder actionBuilder(final Action.Builder actionBuilder) {
this.actionBuilder = actionBuilder;
return this;
}
public PrivateFlatTrace.Builder traceAddress(final List<Integer> traceAddress) {
this.traceAddress = traceAddress;
return this;
}
public PrivateFlatTrace.Builder type(final String type) {
this.type = type;
return this;
}
public PrivateFlatTrace.Builder blockNumber(final Long blockNumber) {
this.blockNumber = blockNumber;
return this;
}
public PrivateFlatTrace.Builder blockHash(final String blockHash) {
this.blockHash = blockHash;
return this;
}
public PrivateFlatTrace.Builder transactionHash(final String transactionHash) {
this.transactionHash = transactionHash;
return this;
}
public PrivateFlatTrace.Builder error(final Optional<String> error) {
this.error = error;
return this;
}
public PrivateFlatTrace.Builder revertReason(final String revertReason) {
this.revertReason = revertReason;
return this;
}
public String getType() {
return type;
}
public int getSubtraces() {
return subtraces;
}
public List<Integer> getTraceAddress() {
return traceAddress;
}
public Long getBlockNumber() {
return blockNumber;
}
public String getBlockHash() {
return blockHash;
}
public String getTransactionHash() {
return transactionHash;
}
public Integer getTransactionPosition() {
return transactionPosition;
}
public Optional<String> getError() {
return error;
}
public String getRevertReason() {
return revertReason;
}
void incSubTraces() {
this.subtraces++;
}
public PrivateFlatTrace build() {
return new PrivateFlatTrace(
actionBuilder,
resultBuilder,
subtraces,
traceAddress,
type,
blockNumber,
blockHash,
transactionPosition,
transactionHash,
error,
revertReason);
}
public Result.Builder getResultBuilder() {
return resultBuilder;
}
public Action.Builder getActionBuilder() {
return actionBuilder;
}
}
}

@ -0,0 +1,598 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing;
import static org.hyperledger.besu.evm.internal.Words.toAddress;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateTransactionTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.Trace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.TracingUtils;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.Action;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.Result;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.privacy.ExecutedPrivateTransaction;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.Streams;
import com.google.common.util.concurrent.Atomics;
import org.apache.tuweni.bytes.Bytes;
public class PrivateTraceGenerator {
private static final String ZERO_ADDRESS_STRING = Address.ZERO.toHexString();
private static final int EIP_150_DIVISOR = 64;
public static Stream<Trace> generateFromTransactionTrace(
final ProtocolSchedule protocolSchedule,
final PrivateTransactionTrace transactionTrace,
final Block block,
final AtomicInteger traceCounter,
final Consumer<PrivateFlatTrace.Builder> consumer) {
final PrivateFlatTrace.Builder firstFlatTraceBuilder =
PrivateFlatTrace.freshBuilder(transactionTrace);
final ExecutedPrivateTransaction tx = transactionTrace.getPrivateTransaction();
final Optional<String> smartContractCode =
tx.getInit().map(__ -> transactionTrace.getResult().getOutput().toString());
final Optional<String> smartContractAddress =
smartContractCode.map(
__ -> Address.contractAddress(tx.getSender(), tx.getNonce()).toHexString());
final Optional<Bytes> revertReason = transactionTrace.getResult().getRevertReason();
// set code field in result node
smartContractCode.ifPresent(firstFlatTraceBuilder.getResultBuilder()::code);
revertReason.ifPresent(r -> firstFlatTraceBuilder.revertReason(r.toHexString()));
// set init field if transaction is a smart contract deployment
tx.getInit().map(Bytes::toHexString).ifPresent(firstFlatTraceBuilder.getActionBuilder()::init);
// set to, input and callType fields if not a smart contract
if (tx.getTo().isPresent()) {
final Bytes payload = tx.getPayload();
firstFlatTraceBuilder
.getActionBuilder()
.to(tx.getTo().map(Bytes::toHexString).orElse(null))
.callType("call")
.input(payload == null ? "0x" : payload.toHexString());
if (!transactionTrace.getTraceFrames().isEmpty()
&& hasRevertInSubCall(transactionTrace, transactionTrace.getTraceFrames().get(0))) {
firstFlatTraceBuilder.error(Optional.of("Reverted"));
}
} else {
firstFlatTraceBuilder
.type("create")
.getResultBuilder()
.address(smartContractAddress.orElse(null));
}
if (!transactionTrace.getTraceFrames().isEmpty()) {
final OptionalLong precompiledGasCost =
transactionTrace.getTraceFrames().get(0).getPrecompiledGasCost();
if (precompiledGasCost.isPresent()) {
firstFlatTraceBuilder
.getResultBuilder()
.gasUsed("0x" + Long.toHexString(precompiledGasCost.getAsLong()));
}
}
final List<PrivateFlatTrace.Builder> flatTraces = new ArrayList<>();
// stack of previous contexts
final Deque<PrivateFlatTrace.Context> tracesContexts = new ArrayDeque<>();
// add the first transactionTrace context to the queue of transactionTrace contexts
PrivateFlatTrace.Context currentContext = new PrivateFlatTrace.Context(firstFlatTraceBuilder);
tracesContexts.addLast(currentContext);
flatTraces.add(currentContext.getBuilder());
// declare the first transactionTrace context as the previous transactionTrace context
long cumulativeGasCost = 0;
final Iterator<TraceFrame> iter = transactionTrace.getTraceFrames().iterator();
Optional<TraceFrame> nextTraceFrame =
iter.hasNext() ? Optional.of(iter.next()) : Optional.empty();
while (nextTraceFrame.isPresent()) {
final TraceFrame traceFrame = nextTraceFrame.get();
nextTraceFrame = iter.hasNext() ? Optional.of(iter.next()) : Optional.empty();
cumulativeGasCost +=
traceFrame.getGasCost().orElse(0L) + traceFrame.getPrecompiledGasCost().orElse(0L);
final String opcodeString = traceFrame.getOpcode();
if ("CALL".equals(opcodeString)
|| "CALLCODE".equals(opcodeString)
|| "DELEGATECALL".equals(opcodeString)
|| "STATICCALL".equals(opcodeString)) {
currentContext =
handleCall(
transactionTrace,
traceFrame,
nextTraceFrame,
flatTraces,
cumulativeGasCost,
tracesContexts,
opcodeString.toLowerCase(Locale.US));
} else if ("CALLDATALOAD".equals(opcodeString)) {
currentContext = handleCallDataLoad(currentContext, traceFrame);
} else if ("RETURN".equals(opcodeString) || "STOP".equals(opcodeString)) {
currentContext =
handleReturn(
protocolSchedule,
transactionTrace,
block,
traceFrame,
tracesContexts,
currentContext);
} else if ("SELFDESTRUCT".equals(opcodeString)) {
if (traceFrame.getExceptionalHaltReason().isPresent()) {
currentContext =
handleCall(
transactionTrace,
traceFrame,
nextTraceFrame,
flatTraces,
cumulativeGasCost,
tracesContexts,
opcodeString.toLowerCase(Locale.US));
} else {
currentContext =
handleSelfDestruct(traceFrame, tracesContexts, currentContext, flatTraces);
}
} else if (("CREATE".equals(opcodeString) || "CREATE2".equals(opcodeString))
&& (traceFrame.getExceptionalHaltReason().isEmpty() || traceFrame.getDepth() == 0)) {
currentContext =
handleCreateOperation(
traceFrame,
nextTraceFrame,
flatTraces,
cumulativeGasCost,
tracesContexts,
smartContractAddress);
} else if ("REVERT".equals(opcodeString)) {
currentContext = handleRevert(tracesContexts, currentContext);
}
if (traceFrame.getExceptionalHaltReason().isPresent()) {
currentContext = handleHalt(flatTraces, tracesContexts, currentContext, traceFrame);
}
if (currentContext == null) {
break;
}
}
return flatTraces.stream().peek(consumer).map(PrivateFlatTrace.Builder::build);
}
public static Stream<Trace> generateFromTransactionTraceAndBlock(
final ProtocolSchedule protocolSchedule,
final PrivateTransactionTrace transactionTrace,
final Block block) {
return generateFromTransactionTrace(
protocolSchedule,
transactionTrace,
block,
new AtomicInteger(),
builder ->
addAdditionalTransactionInformationToFlatTrace(builder, transactionTrace, block));
}
private static long computeGasUsed(
final Deque<PrivateFlatTrace.Context> tracesContexts,
final PrivateFlatTrace.Context currentContext,
final PrivateTransactionTrace transactionTrace,
final TraceFrame traceFrame) {
final long gasRemainingBeforeProcessed;
final long gasRemainingAfterProcessed;
long gasRefund = 0;
if (tracesContexts.size() == 1) {
gasRemainingBeforeProcessed = transactionTrace.getTraceFrames().get(0).getGasRemaining();
gasRemainingAfterProcessed = transactionTrace.getResult().getGasRemaining();
if (gasRemainingAfterProcessed > traceFrame.getGasRemaining()) {
gasRefund = gasRemainingAfterProcessed - traceFrame.getGasRemaining();
} else {
gasRefund = traceFrame.getGasRefund();
}
} else {
final Action.Builder actionBuilder = currentContext.getBuilder().getActionBuilder();
gasRemainingBeforeProcessed = Long.decode(actionBuilder.getGas());
gasRemainingAfterProcessed = traceFrame.getGasRemaining();
}
return gasRemainingBeforeProcessed - gasRemainingAfterProcessed + gasRefund;
}
private static long computeGas(
final TraceFrame traceFrame, final Optional<TraceFrame> nextTraceFrame) {
if (traceFrame.getGasCost().isPresent()) {
final long gasNeeded = traceFrame.getGasCost().getAsLong();
final long currentGas = traceFrame.getGasRemaining();
if (currentGas >= gasNeeded) {
final long gasRemaining = currentGas - gasNeeded;
return gasRemaining - Math.floorDiv(gasRemaining, EIP_150_DIVISOR);
}
}
return nextTraceFrame.map(TraceFrame::getGasRemaining).orElse(0L);
}
private static String calculateCallingAddress(final PrivateFlatTrace.Context lastContext) {
final PrivateFlatTrace.Builder lastContextBuilder = lastContext.getBuilder();
final Action.Builder lastActionBuilder = lastContextBuilder.getActionBuilder();
if (lastActionBuilder.getCallType() == null) {
if ("create".equals(lastContextBuilder.getType())) {
return lastContextBuilder.getResultBuilder().getAddress();
} else {
return ZERO_ADDRESS_STRING;
}
}
switch (lastActionBuilder.getCallType()) {
case "call":
case "staticcall":
return lastActionBuilder.getTo();
case "delegatecall":
case "callcode":
return lastActionBuilder.getFrom();
case "create":
case "create2":
return lastContextBuilder.getResultBuilder().getAddress();
default:
return ZERO_ADDRESS_STRING;
}
}
private static List<Integer> calculateTraceAddress(
final Deque<PrivateFlatTrace.Context> contexts) {
return contexts.stream()
.map(context -> context.getBuilder().getSubtraces())
.collect(Collectors.toList());
}
private static List<Integer> calculateSelfDescructAddress(
final Deque<PrivateFlatTrace.Context> contexts) {
return Streams.concat(
contexts.stream()
.map(context -> context.getBuilder().getSubtraces())) // , Stream.of(0))
.collect(Collectors.toList());
}
private static String getActionAddress(
final Action.Builder callingAction, final String recipient) {
if (callingAction.getCallType() != null) {
return callingAction.getCallType().equals("call")
? callingAction.getTo()
: callingAction.getFrom();
}
return firstNonNull("", recipient, callingAction.getFrom(), callingAction.getTo());
}
private static String firstNonNull(final String defaultValue, final String... values) {
for (final String value : values) {
if (value != null) {
return value;
}
}
return defaultValue;
}
private static PrivateFlatTrace.Context handleCreateOperation(
final TraceFrame traceFrame,
final Optional<TraceFrame> nextTraceFrame,
final List<PrivateFlatTrace.Builder> flatTraces,
final long cumulativeGasCost,
final Deque<PrivateFlatTrace.Context> tracesContexts,
final Optional<String> smartContractAddress) {
final PrivateFlatTrace.Context lastContext = tracesContexts.peekLast();
final String callingAddress = calculateCallingAddress(lastContext);
final PrivateFlatTrace.Builder subTraceBuilder =
PrivateFlatTrace.builder()
.type("create")
.traceAddress(calculateTraceAddress(tracesContexts))
.resultBuilder(Result.builder());
final Action.Builder subTraceActionBuilder =
Action.builder()
.from(smartContractAddress.orElse(callingAddress))
.gas("0x" + Long.toHexString(computeGas(traceFrame, nextTraceFrame)))
.value(Quantity.create(nextTraceFrame.map(TraceFrame::getValue).orElse(Wei.ZERO)));
traceFrame
.getMaybeCode()
.map(Code::getBytes)
.map(Bytes::toHexString)
.ifPresent(subTraceActionBuilder::init);
final PrivateFlatTrace.Context currentContext =
new PrivateFlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder));
currentContext
.getBuilder()
.getResultBuilder()
.address(nextTraceFrame.map(TraceFrame::getRecipient).orElse(Address.ZERO).toHexString());
currentContext.setCreateOp(true);
currentContext.decGasUsed(cumulativeGasCost);
tracesContexts.addLast(currentContext);
flatTraces.add(currentContext.getBuilder());
return currentContext;
}
private static PrivateFlatTrace.Context handleHalt(
final List<PrivateFlatTrace.Builder> flatTraces,
final Deque<PrivateFlatTrace.Context> tracesContexts,
final PrivateFlatTrace.Context currentContext,
final TraceFrame traceFrame) {
final PrivateFlatTrace.Builder traceFrameBuilder;
if (currentContext == null) {
traceFrameBuilder = flatTraces.get(flatTraces.size() - 1);
} else {
traceFrameBuilder = currentContext.getBuilder();
}
traceFrameBuilder.error(
traceFrame.getExceptionalHaltReason().map(ExceptionalHaltReason::getDescription));
if (currentContext != null) {
final Action.Builder actionBuilder = traceFrameBuilder.getActionBuilder();
actionBuilder.value(Quantity.create(traceFrame.getValue()));
tracesContexts.removeLast();
final PrivateFlatTrace.Context nextContext = tracesContexts.peekLast();
if (nextContext != null) {
nextContext.getBuilder().incSubTraces();
}
return nextContext;
}
return currentContext;
}
private static PrivateFlatTrace.Context handleRevert(
final Deque<PrivateFlatTrace.Context> tracesContexts,
final PrivateFlatTrace.Context currentContext) {
currentContext.getBuilder().error(Optional.of("Reverted"));
tracesContexts.removeLast();
final PrivateFlatTrace.Context nextContext = tracesContexts.peekLast();
if (nextContext != null) {
nextContext.getBuilder().incSubTraces();
}
return nextContext;
}
private static PrivateFlatTrace.Context handleSelfDestruct(
final TraceFrame traceFrame,
final Deque<PrivateFlatTrace.Context> tracesContexts,
final PrivateFlatTrace.Context currentContext,
final List<PrivateFlatTrace.Builder> flatTraces) {
final Action.Builder actionBuilder = currentContext.getBuilder().getActionBuilder();
final long gasUsed =
Long.decode(actionBuilder.getGas())
- traceFrame.getGasRemaining()
+ (traceFrame.getGasCost().orElse(0L));
currentContext.setGasUsed(gasUsed);
final Bytes[] stack = traceFrame.getStack().orElseThrow();
final Address refundAddress = toAddress(stack[stack.length - 1]);
final PrivateFlatTrace.Builder subTraceBuilder =
PrivateFlatTrace.builder()
.type("suicide")
.traceAddress(calculateSelfDescructAddress(tracesContexts));
final AtomicReference<Wei> weiBalance = Atomics.newReference(Wei.ZERO);
traceFrame
.getMaybeRefunds()
.ifPresent(refunds -> weiBalance.set(refunds.getOrDefault(refundAddress, Wei.ZERO)));
final Action.Builder callingAction = tracesContexts.peekLast().getBuilder().getActionBuilder();
final String actionAddress =
getActionAddress(callingAction, traceFrame.getRecipient().toHexString());
final Action.Builder subTraceActionBuilder =
Action.builder()
.address(actionAddress)
.refundAddress(refundAddress.toString())
.balance(TracingUtils.weiAsHex(weiBalance.get()));
flatTraces.add(
new PrivateFlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder))
.getBuilder());
final PrivateFlatTrace.Context lastContext = tracesContexts.removeLast();
lastContext.getBuilder().incSubTraces();
final PrivateFlatTrace.Context nextContext = tracesContexts.peekLast();
if (nextContext != null) {
nextContext.getBuilder().incSubTraces();
}
return nextContext;
}
private static PrivateFlatTrace.Context handleReturn(
final ProtocolSchedule protocolSchedule,
final PrivateTransactionTrace transactionTrace,
final Block block,
final TraceFrame traceFrame,
final Deque<PrivateFlatTrace.Context> tracesContexts,
final PrivateFlatTrace.Context currentContext) {
final PrivateFlatTrace.Builder traceFrameBuilder = currentContext.getBuilder();
final Result.Builder resultBuilder = traceFrameBuilder.getResultBuilder();
final Action.Builder actionBuilder = traceFrameBuilder.getActionBuilder();
actionBuilder.value(Quantity.create(traceFrame.getValue()));
currentContext.setGasUsed(
computeGasUsed(tracesContexts, currentContext, transactionTrace, traceFrame));
if ("STOP".equals(traceFrame.getOpcode()) && resultBuilder.isGasUsedEmpty()) {
final long callStipend =
protocolSchedule
.getByBlockHeader(block.getHeader())
.getGasCalculator()
.getAdditionalCallStipend();
tracesContexts.stream()
.filter(
context ->
!tracesContexts.getFirst().equals(context)
&& !tracesContexts.getLast().equals(context))
.forEach(context -> context.decGasUsed(callStipend));
}
final Bytes outputData = traceFrame.getOutputData();
if (resultBuilder.getCode() == null) {
resultBuilder.output(outputData.toHexString());
}
// set value for contract creation TXes, CREATE, and CREATE2
if (actionBuilder.getCallType() == null && traceFrame.getMaybeCode().isPresent()) {
actionBuilder.init(traceFrame.getMaybeCode().get().getBytes().toHexString());
resultBuilder.code(outputData.toHexString());
if (currentContext.isCreateOp()) {
// this is from a CREATE/CREATE2, so add code deposit cost.
currentContext.incGasUsed(outputData.size() * 200L);
}
}
tracesContexts.removeLast();
final PrivateFlatTrace.Context nextContext = tracesContexts.peekLast();
if (nextContext != null) {
nextContext.getBuilder().incSubTraces();
}
return nextContext;
}
private static PrivateFlatTrace.Context handleCallDataLoad(
final PrivateFlatTrace.Context currentContext, final TraceFrame traceFrame) {
if (!traceFrame.getValue().isZero()) {
currentContext
.getBuilder()
.getActionBuilder()
.value(traceFrame.getValue().toShortHexString());
} else {
currentContext.getBuilder().getActionBuilder().value("0x0");
}
return currentContext;
}
private static PrivateFlatTrace.Context handleCall(
final PrivateTransactionTrace transactionTrace,
final TraceFrame traceFrame,
final Optional<TraceFrame> nextTraceFrame,
final List<PrivateFlatTrace.Builder> flatTraces,
final long cumulativeGasCost,
final Deque<PrivateFlatTrace.Context> tracesContexts,
final String opcodeString) {
final Bytes[] stack = traceFrame.getStack().orElseThrow();
final PrivateFlatTrace.Context lastContext = tracesContexts.peekLast();
final String callingAddress = calculateCallingAddress(lastContext);
if (traceFrame.getDepth() >= nextTraceFrame.map(TraceFrame::getDepth).orElse(0)) {
// don't log calls to calls that don't execute, such as insufficient value and precompiles
return tracesContexts.peekLast();
}
final PrivateFlatTrace.Builder subTraceBuilder =
PrivateFlatTrace.builder()
.traceAddress(calculateTraceAddress(tracesContexts))
.resultBuilder(Result.builder());
final Action.Builder subTraceActionBuilder =
Action.builder()
.from(callingAddress)
.input(
nextTraceFrame.map(TraceFrame::getInputData).map(Bytes::toHexString).orElse(null))
.gas(
"0x" + Long.toHexString(nextTraceFrame.map(TraceFrame::getGasRemaining).orElse(0L)))
.callType(opcodeString.toLowerCase(Locale.US))
.value(Quantity.create(traceFrame.getValue()));
if (stack.length > 1) {
subTraceActionBuilder.to(toAddress(stack[stack.length - 2]).toString());
}
nextTraceFrame.ifPresent(
nextFrame -> {
if (hasRevertInSubCall(transactionTrace, nextFrame)) {
subTraceBuilder.error(Optional.of("Reverted"));
}
});
final PrivateFlatTrace.Context currentContext =
new PrivateFlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder));
currentContext.decGasUsed(cumulativeGasCost);
tracesContexts.addLast(currentContext);
flatTraces.add(currentContext.getBuilder());
return currentContext;
}
private static boolean hasRevertInSubCall(
final PrivateTransactionTrace transactionTrace, final TraceFrame callFrame) {
return transactionTrace.getTraceFrames().stream()
.filter(traceFrame -> !traceFrame.equals(callFrame))
.takeWhile(traceFrame -> !traceFrame.getOpcode().equals("RETURN"))
.filter(traceFrame -> traceFrame.getOpcode().equals("REVERT"))
.anyMatch(traceFrame -> traceFrame.getDepth() == callFrame.getDepth());
}
private static void addAdditionalTransactionInformationToFlatTrace(
final PrivateFlatTrace.Builder builder,
final PrivateTransactionTrace transactionTrace,
final Block block) {
// add block information (hash and number)
builder.blockHash(block.getHash().toHexString()).blockNumber(block.getHeader().getNumber());
// add transaction information (position and hash)
builder.transactionHash(transactionTrace.getPrivateTransaction().getPmtHash().toHexString());
addContractCreationMethodToTrace(transactionTrace, builder);
}
private static void addContractCreationMethodToTrace(
final PrivateTransactionTrace transactionTrace, final PrivateFlatTrace.Builder builder) {
// add creationMethod for create action
Optional.ofNullable(builder.getType())
.filter(type -> type.equals("create"))
.ifPresent(
__ ->
builder
.getActionBuilder()
.creationMethod(
transactionTrace.getTraceFrames().stream()
.filter(frame -> "CREATE2".equals(frame.getOpcode()))
.findFirst()
.map(TraceFrame::getOpcode)
.orElse("CREATE")
.toLowerCase(Locale.US)));
}
}

@ -263,7 +263,7 @@ public class Action {
return this; return this;
} }
Builder refundAddress(final String refundAddress) { public Builder refundAddress(final String refundAddress) {
this.refundAddress = refundAddress; this.refundAddress = refundAddress;
return this; return this;
} }

@ -624,7 +624,7 @@ public class FlatTraceGenerator {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private static void addAdditionalTransactionInformationToFlatTrace( protected static void addAdditionalTransactionInformationToFlatTrace(
final FlatTrace.Builder builder, final TransactionTrace transactionTrace, final Block block) { final FlatTrace.Builder builder, final TransactionTrace transactionTrace, final Block block) {
// add block information (hash and number) // add block information (hash and number)
builder.blockHash(block.getHash().toHexString()).blockNumber(block.getHeader().getNumber()); builder.blockHash(block.getHash().toHexString()).blockNumber(block.getHeader().getNumber());

@ -34,6 +34,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.P
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetTransactionCount; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetTransactionCount;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetTransactionReceipt; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetTransactionReceipt;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivNewFilter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivNewFilter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivTraceTransaction;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockReplay;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockTracer;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
@ -68,6 +71,9 @@ public class PrivJsonRpcMethods extends PrivacyApiGroupJsonRpcMethods {
final PrivacyIdProvider privacyIdProvider, final PrivacyIdProvider privacyIdProvider,
final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) { final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) {
final PrivateBlockReplay blockReplay =
new PrivateBlockReplay(
getProtocolSchedule(), getBlockchainQueries().getBlockchain(), privacyController);
final Map<String, JsonRpcMethod> RPC_METHODS = final Map<String, JsonRpcMethod> RPC_METHODS =
mapOf( mapOf(
new PrivCall(getBlockchainQueries(), privacyController, privacyIdProvider), new PrivCall(getBlockchainQueries(), privacyController, privacyIdProvider),
@ -89,7 +95,15 @@ public class PrivJsonRpcMethods extends PrivacyApiGroupJsonRpcMethods {
new PrivGetFilterLogs(filterManager, privacyController, privacyIdProvider), new PrivGetFilterLogs(filterManager, privacyController, privacyIdProvider),
new PrivGetFilterChanges(filterManager, privacyController, privacyIdProvider), new PrivGetFilterChanges(filterManager, privacyController, privacyIdProvider),
new PrivNewFilter(filterManager, privacyController, privacyIdProvider), new PrivNewFilter(filterManager, privacyController, privacyIdProvider),
new PrivUninstallFilter(filterManager, privacyController, privacyIdProvider)); new PrivUninstallFilter(filterManager, privacyController, privacyIdProvider),
new PrivTraceTransaction(
() -> new PrivateBlockTracer(blockReplay),
getBlockchainQueries(),
getProtocolSchedule(),
getPrivacyQueries(),
privacyController,
getPrivacyParameters(),
privacyIdProvider));
if (!getPrivacyParameters().isFlexiblePrivacyGroupsEnabled()) { if (!getPrivacyParameters().isFlexiblePrivacyGroupsEnabled()) {
final Map<String, JsonRpcMethod> OFFCHAIN_METHODS = final Map<String, JsonRpcMethod> OFFCHAIN_METHODS =

@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.LogWithMetadata; import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt;
import org.hyperledger.besu.ethereum.privacy.PrivateWorldStateReader; import org.hyperledger.besu.ethereum.privacy.PrivateWorldStateReader;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata;
import java.util.Collection; import java.util.Collection;
@ -43,6 +44,11 @@ public class PrivacyQueries {
this.privateWorldStateReader = privateWorldStateReader; this.privateWorldStateReader = privateWorldStateReader;
} }
public Optional<PrivateBlockMetadata> getPrivateBlockMetaData(
final String privacyGroupId, final Hash blockHash) {
return privateWorldStateReader.getPrivateBlockMetadata(privacyGroupId, blockHash);
}
public List<LogWithMetadata> matchingLogs( public List<LogWithMetadata> matchingLogs(
final String privacyGroupId, final String privacyGroupId,
final long fromBlockNumber, final long fromBlockNumber,

@ -44,7 +44,6 @@ public class PrivJsonRpcMethodsTest {
@Mock private TransactionPool transactionPool; @Mock private TransactionPool transactionPool;
@Mock private PrivacyParameters privacyParameters; @Mock private PrivacyParameters privacyParameters;
@Mock private FilterManager filterManager; @Mock private FilterManager filterManager;
private PrivJsonRpcMethods privJsonRpcMethods; private PrivJsonRpcMethods privJsonRpcMethods;
@BeforeEach @BeforeEach

@ -355,6 +355,16 @@ public class PrivateTransaction implements org.hyperledger.besu.plugin.data.Priv
return payload; return payload;
} }
/**
* Returns the payload if this is a contract creation transaction.
*
* @return if present the init code
*/
@Override
public Optional<Bytes> getInit() {
return getTo().isPresent() ? Optional.empty() : Optional.of(payload);
}
/** /**
* Return the transaction chain id (if it exists) * Return the transaction chain id (if it exists)
* *

@ -66,6 +66,12 @@ public class PrivateWorldStateReader {
.orElse(Collections.emptyList()); .orElse(Collections.emptyList());
} }
public Optional<PrivateBlockMetadata> getPrivateBlockMetadata(
final String privacyGroupId, final Hash blockHash) {
final Bytes32 privacyGroupIdBytes = Bytes32.wrap(Bytes.fromBase64String(privacyGroupId));
return privateStateStorage.getPrivateBlockMetadata(blockHash, privacyGroupIdBytes);
}
public Optional<PrivateTransactionReceipt> getPrivateTransactionReceipt( public Optional<PrivateTransactionReceipt> getPrivateTransactionReceipt(
final Hash blockHash, final Hash transactionHash) { final Hash blockHash, final Hash transactionHash) {
return privateStateStorage.getTransactionReceipt(blockHash, transactionHash); return privateStateStorage.getTransactionReceipt(blockHash, transactionHash);

@ -70,7 +70,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) { tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought" description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files files = sourceSets.main.allJava.files
knownHash = 'I851CCOs00yYpW10qIGIak1bKbYhKFQkV2wyCYELHKY=' knownHash = 'W1gv5UjqU+RJZJN6xPNjVfjuz7nKIcBgmh1j2XON4EU='
} }
check.dependsOn('checkAPIChanges') check.dependsOn('checkAPIChanges')

@ -94,6 +94,16 @@ public interface PrivateTransaction {
*/ */
Address getSender(); Address getSender();
/**
* An unlimited size byte array specifying the EVM-code for the account initialization procedure.
*
* <p>Only present if this is a contract creation transaction, which is only true if {@link
* #getTo} is empty.
*
* @return if present, the contract init code.
*/
Optional<Bytes> getInit();
/** /**
* The chainId, computed from the 'V' portion of the signature. Used for replay protection. If * The chainId, computed from the 'V' portion of the signature. Used for replay protection. If
* replay protection is not enabled this value will not be present. * replay protection is not enabled this value will not be present.

Loading…
Cancel
Save