mirror of https://github.com/hyperledger/besu
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
parent
290f21c227
commit
50f8add052
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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)); |
||||||
|
} |
||||||
|
} |
@ -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))); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue