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