Add debug_accountAt RPC end point (#1981)

* Add debug_accountAt RPC end point

Signed-off-by: David Mechler <david.mechler@consensys.net>

* PR comment

Signed-off-by: David Mechler <david.mechler@consensys.net>

Co-authored-by: Antoine Toulme <antoine@lunar-ocean.com>
Co-authored-by: Sally MacFarlane <sally.macfarlane@consensys.net>
pull/2234/head
David Mechler 4 years ago committed by GitHub
parent a366379629
commit b4e14bc356
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java
  2. 123
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAt.java
  3. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java
  4. 28
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/DebugAccountAtResult.java
  5. 7
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java
  6. 168
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAtTest.java
  7. 23
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/debug_accountAt.json

@ -32,6 +32,7 @@ public enum RpcMethod {
CLIQUE_GET_PROPOSALS("clique_proposals"), CLIQUE_GET_PROPOSALS("clique_proposals"),
CLIQUE_PROPOSE("clique_propose"), CLIQUE_PROPOSE("clique_propose"),
CLIQUE_GET_SIGNER_METRICS("clique_getSignerMetrics"), CLIQUE_GET_SIGNER_METRICS("clique_getSignerMetrics"),
DEBUG_ACCOUNT_AT("debug_accountAt"),
DEBUG_METRICS("debug_metrics"), DEBUG_METRICS("debug_metrics"),
DEBUG_STORAGE_RANGE_AT("debug_storageRangeAt"), DEBUG_STORAGE_RANGE_AT("debug_storageRangeAt"),
DEBUG_TRACE_BLOCK("debug_traceBlock"), DEBUG_TRACE_BLOCK("debug_traceBlock"),

@ -0,0 +1,123 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
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.parameters.BlockParameterOrBlockHash;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.ImmutableDebugAccountAtResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.debug.TraceOptions;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
public class DebugAccountAt extends AbstractBlockParameterOrBlockHashMethod {
private final Supplier<BlockTracer> blockTracerSupplier;
public DebugAccountAt(
final BlockchainQueries blockchainQueries, final Supplier<BlockTracer> blockTracerSupplier) {
super(blockchainQueries);
this.blockTracerSupplier = blockTracerSupplier;
}
public DebugAccountAt(
final Supplier<BlockchainQueries> blockchainQueries,
final Supplier<BlockTracer> blockTracerSupplier) {
super(blockchainQueries);
this.blockTracerSupplier = blockTracerSupplier;
}
@Override
public String getName() {
return RpcMethod.DEBUG_ACCOUNT_AT.getMethodName();
}
@Override
protected BlockParameterOrBlockHash blockParameterOrBlockHash(
final JsonRpcRequestContext requestContext) {
return requestContext.getRequiredParameter(0, BlockParameterOrBlockHash.class);
}
@Override
protected Object resultByBlockHash(
final JsonRpcRequestContext requestContext, final Hash blockHash) {
final Integer txIndex = requestContext.getRequiredParameter(1, Integer.class);
final Address address = requestContext.getRequiredParameter(2, Address.class);
Optional<BlockWithMetadata<TransactionWithMetadata, Hash>> block =
blockchainQueries.get().blockByHash(blockHash);
if (block.isEmpty()) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.BLOCK_NOT_FOUND);
}
List<TransactionWithMetadata> transactions = block.get().getTransactions();
if (transactions.isEmpty() || txIndex < 0 || txIndex > block.get().getTransactions().size()) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS);
}
final Optional<TransactionTrace> transactionTrace =
blockTracerSupplier.get()
.trace(blockHash, new DebugOperationTracer(new TraceOptions(false, true, true)))
.map(BlockTrace::getTransactionTraces).orElse(Collections.emptyList()).stream()
.filter(
trxTrace ->
trxTrace
.getTransaction()
.getHash()
.equals(transactions.get(txIndex).getTransaction().getHash()))
.findFirst();
if (transactionTrace.isEmpty()) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.TRANSACTION_NOT_FOUND);
}
Optional<Account> account =
transactionTrace.get().getTraceFrames().stream()
.map(traceFrame -> traceFrame.getWorldUpdater().get(address))
.filter(Objects::nonNull)
.filter(a -> a.getAddress().equals(address))
.findFirst();
if (account.isEmpty()) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.NO_ACCOUNT_FOUND);
}
return ImmutableDebugAccountAtResult.builder()
.code(Quantity.create(account.get().getCode()))
.nonce(Quantity.create(account.get().getNonce()))
.balance(Quantity.create(account.get().getBalance()))
.codehash(Quantity.create(account.get().getCodeHash()))
.build();
}
}

@ -64,6 +64,7 @@ public enum JsonRpcError {
TX_FEECAP_EXCEEDED(-32000, "Transaction fee cap exceeded"), TX_FEECAP_EXCEEDED(-32000, "Transaction fee cap exceeded"),
REVERT_ERROR(-32000, "Execution reverted"), REVERT_ERROR(-32000, "Execution reverted"),
GAS_PRICE_MUST_BE_ZERO(-3200, "gasPrice must be set to zero on a GoQuorum compatible network"), GAS_PRICE_MUST_BE_ZERO(-3200, "gasPrice must be set to zero on a GoQuorum compatible network"),
TRANSACTION_NOT_FOUND(-32000, "Transaction not found"),
// Miner failures // Miner failures
COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase"), COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase"),

@ -0,0 +1,28 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;
import org.immutables.value.Value;
@Value.Immutable
public abstract class DebugAccountAtResult implements JsonRpcResult {
public abstract String getCode();
public abstract String getNonce();
public abstract String getBalance();
public abstract String getCodehash();
}

@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.methods;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugAccountAt;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugAccountRange; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugAccountRange;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugBatchSendRawTransaction; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugBatchSendRawTransaction;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugGetBadBlocks; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugGetBadBlocks;
@ -93,9 +94,7 @@ public class DebugJsonRpcMethods extends ApiGroupJsonRpcMethods {
new DebugStandardTraceBlockToFile( new DebugStandardTraceBlockToFile(
() -> new TransactionTracer(blockReplay), blockchainQueries, dataDir), () -> new TransactionTracer(blockReplay), blockchainQueries, dataDir),
new DebugStandardTraceBadBlockToFile( new DebugStandardTraceBadBlockToFile(
() -> new TransactionTracer(blockReplay), () -> new TransactionTracer(blockReplay), blockchainQueries, protocolSchedule, dataDir),
blockchainQueries, new DebugAccountAt(blockchainQueries, () -> new BlockTracer(blockReplay)));
protocolSchedule,
dataDir));
} }
} }

@ -0,0 +1,168 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
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.query.BlockWithMetadata;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Transaction;
import java.util.Collections;
import java.util.Optional;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class DebugAccountAtTest {
private final BlockTracer blockTracer = Mockito.mock(BlockTracer.class);
private final BlockchainQueries blockchainQueries = Mockito.mock(BlockchainQueries.class);
@SuppressWarnings("unchecked")
private final BlockWithMetadata<TransactionWithMetadata, Hash> blockWithMetadata =
Mockito.mock(BlockWithMetadata.class);
private final TransactionWithMetadata transactionWithMetadata =
Mockito.mock(TransactionWithMetadata.class);
private final BlockTrace blockTrace = Mockito.mock(BlockTrace.class);
private final TransactionTrace transactionTrace = Mockito.mock(TransactionTrace.class);
private final DebugAccountAt debugAccountAt =
new DebugAccountAt(blockchainQueries, () -> blockTracer);
private final Transaction transaction = Mockito.mock(Transaction.class);
@Test
public void nameShouldBeDebugAccountAt() {
Assertions.assertThat(debugAccountAt.getName()).isEqualTo("debug_accountAt");
}
@Test
public void testBlockNotFoundResponse() {
Mockito.when(blockchainQueries.blockByHash(Mockito.any())).thenReturn(Optional.empty());
final Object[] params = new Object[] {Hash.ZERO.toHexString(), 0, Address.ZERO.toHexString()};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_accountAt", params));
final JsonRpcResponse response = debugAccountAt.response(request);
Assertions.assertThat(response instanceof JsonRpcErrorResponse).isTrue();
Assertions.assertThat(((JsonRpcErrorResponse) response).getError())
.isEqualByComparingTo(JsonRpcError.BLOCK_NOT_FOUND);
}
@Test
public void testInvalidParamsResponseEmptyList() {
Mockito.when(blockchainQueries.blockByHash(Mockito.any()))
.thenReturn(Optional.of(blockWithMetadata));
Mockito.when(blockWithMetadata.getTransactions()).thenReturn(Collections.emptyList());
final Object[] params = new Object[] {Hash.ZERO.toHexString(), 0, Address.ZERO.toHexString()};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_accountAt", params));
final JsonRpcResponse response = debugAccountAt.response(request);
Assertions.assertThat(response instanceof JsonRpcErrorResponse).isTrue();
Assertions.assertThat(((JsonRpcErrorResponse) response).getError())
.isEqualByComparingTo(JsonRpcError.INVALID_PARAMS);
}
@Test
public void testInvalidParamsResponseNegative() {
Mockito.when(blockchainQueries.blockByHash(Mockito.any()))
.thenReturn(Optional.of(blockWithMetadata));
Mockito.when(blockWithMetadata.getTransactions())
.thenReturn(Collections.singletonList(transactionWithMetadata));
final Object[] params = new Object[] {Hash.ZERO.toHexString(), -1, Address.ZERO.toHexString()};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_accountAt", params));
final JsonRpcResponse response = debugAccountAt.response(request);
Assertions.assertThat(response instanceof JsonRpcErrorResponse).isTrue();
Assertions.assertThat(((JsonRpcErrorResponse) response).getError())
.isEqualByComparingTo(JsonRpcError.INVALID_PARAMS);
}
@Test
public void testInvalidParamsResponseTooHigh() {
Mockito.when(blockchainQueries.blockByHash(Mockito.any()))
.thenReturn(Optional.of(blockWithMetadata));
Mockito.when(blockWithMetadata.getTransactions())
.thenReturn(Collections.singletonList(transactionWithMetadata));
final Object[] params = new Object[] {Hash.ZERO.toHexString(), 2, Address.ZERO.toHexString()};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_accountAt", params));
final JsonRpcResponse response = debugAccountAt.response(request);
Assertions.assertThat(response instanceof JsonRpcErrorResponse).isTrue();
Assertions.assertThat(((JsonRpcErrorResponse) response).getError())
.isEqualByComparingTo(JsonRpcError.INVALID_PARAMS);
}
@Test
public void testTransactionNotFoundResponse() {
Mockito.when(blockchainQueries.blockByHash(Mockito.any()))
.thenReturn(Optional.of(blockWithMetadata));
Mockito.when(blockWithMetadata.getTransactions())
.thenReturn(Collections.singletonList(transactionWithMetadata));
final Object[] params = new Object[] {Hash.ZERO.toHexString(), 0, Address.ZERO.toHexString()};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_accountAt", params));
final JsonRpcResponse response = debugAccountAt.response(request);
Assertions.assertThat(response instanceof JsonRpcErrorResponse).isTrue();
Assertions.assertThat(((JsonRpcErrorResponse) response).getError())
.isEqualByComparingTo(JsonRpcError.TRANSACTION_NOT_FOUND);
}
@Test
public void testNoAccountFoundResponse() {
Mockito.when(blockchainQueries.blockByHash(Mockito.any()))
.thenReturn(Optional.of(blockWithMetadata));
Mockito.when(blockWithMetadata.getTransactions())
.thenReturn(Collections.singletonList(transactionWithMetadata));
Mockito.when(blockTracer.trace(Mockito.any(Hash.class), Mockito.any()))
.thenReturn(Optional.of(blockTrace));
Mockito.when(blockTrace.getTransactionTraces())
.thenReturn(Collections.singletonList(transactionTrace));
Mockito.when(transactionTrace.getTransaction()).thenReturn(transaction);
Mockito.when(transactionWithMetadata.getTransaction()).thenReturn(transaction);
Mockito.when(transaction.getHash()).thenReturn(Hash.ZERO);
final Object[] params = new Object[] {Hash.ZERO.toHexString(), 0, Address.ZERO.toHexString()};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_accountAt", params));
final JsonRpcResponse response = debugAccountAt.response(request);
Assertions.assertThat(response instanceof JsonRpcErrorResponse).isTrue();
Assertions.assertThat(((JsonRpcErrorResponse) response).getError())
.isEqualByComparingTo(JsonRpcError.NO_ACCOUNT_FOUND);
}
}

@ -0,0 +1,23 @@
{
"request": {
"id": 1,
"jsonrpc": "2.0",
"method": "debug_accountAt",
"params": [
"0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6",
0,
"0xbcde5374fce5edbc8e2a8697c15331677e6ebf0b"
]
},
"response": {
"jsonrpc": "2.0",
"id": 1,
"result": {
"code": "0x0",
"nonce": "0x0",
"balance": "0x340aad21b3b70000",
"codehash" : "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
}
},
"statusCode": 200
}
Loading…
Cancel
Save