mirror of https://github.com/hyperledger/besu
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
parent
a366379629
commit
b4e14bc356
@ -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(); |
||||
} |
||||
} |
@ -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(); |
||||
} |
@ -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…
Reference in new issue