mirror of https://github.com/hyperledger/besu
# Conflicts: # CHANGELOG.md # ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java # ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.javapull/5091/head
commit
8f2f94b6ea
@ -0,0 +1,85 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.engine; |
||||
|
||||
import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
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.ExecutionEngineJsonRpcMethod; |
||||
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.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV1; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import io.vertx.core.Vertx; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class EngineGetPayloadBodiesByHashV1 extends ExecutionEngineJsonRpcMethod { |
||||
|
||||
private static final int MAX_REQUEST_BLOCKS = 1024; |
||||
private static final Logger LOG = LoggerFactory.getLogger(EngineGetPayloadBodiesByHashV1.class); |
||||
private final BlockResultFactory blockResultFactory; |
||||
|
||||
public EngineGetPayloadBodiesByHashV1( |
||||
final Vertx vertx, |
||||
final ProtocolContext protocolContext, |
||||
final BlockResultFactory blockResultFactory, |
||||
final EngineCallListener engineCallListener) { |
||||
super(vertx, protocolContext, engineCallListener); |
||||
this.blockResultFactory = blockResultFactory; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return RpcMethod.ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1.getMethodName(); |
||||
} |
||||
|
||||
@Override |
||||
public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { |
||||
engineCallListener.executionEngineCalled(); |
||||
|
||||
final Object reqId = request.getRequest().getId(); |
||||
|
||||
final Hash[] blockHashes = request.getRequiredParameter(0, Hash[].class); |
||||
|
||||
traceLambda(LOG, "{} parameters: blockHashes {}", () -> getName(), () -> blockHashes); |
||||
|
||||
if (blockHashes.length > getMaxRequestBlocks()) { |
||||
return new JsonRpcErrorResponse(reqId, JsonRpcError.INVALID_RANGE_REQUEST_TOO_LARGE); |
||||
} |
||||
|
||||
final Blockchain blockchain = protocolContext.getBlockchain(); |
||||
|
||||
final EngineGetPayloadBodiesResultV1 engineGetPayloadBodiesResultV1 = |
||||
blockResultFactory.payloadBodiesCompleteV1( |
||||
Arrays.stream(blockHashes).map(blockchain::getBlockBody).collect(Collectors.toList())); |
||||
|
||||
return new JsonRpcSuccessResponse(reqId, engineGetPayloadBodiesResultV1); |
||||
} |
||||
|
||||
protected int getMaxRequestBlocks() { |
||||
return MAX_REQUEST_BLOCKS; |
||||
} |
||||
} |
@ -0,0 +1,113 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.engine; |
||||
|
||||
import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; |
||||
|
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
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.ExecutionEngineJsonRpcMethod; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedLongParameter; |
||||
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.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV1; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
|
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.LongStream; |
||||
|
||||
import io.vertx.core.Vertx; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class EngineGetPayloadBodiesByRangeV1 extends ExecutionEngineJsonRpcMethod { |
||||
private static final Logger LOG = LoggerFactory.getLogger(EngineGetPayloadBodiesByRangeV1.class); |
||||
private static final int MAX_REQUEST_BLOCKS = 1024; |
||||
private final BlockResultFactory blockResultFactory; |
||||
|
||||
public EngineGetPayloadBodiesByRangeV1( |
||||
final Vertx vertx, |
||||
final ProtocolContext protocolContext, |
||||
final BlockResultFactory blockResultFactory, |
||||
final EngineCallListener engineCallListener) { |
||||
super(vertx, protocolContext, engineCallListener); |
||||
this.blockResultFactory = blockResultFactory; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return RpcMethod.ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1.getMethodName(); |
||||
} |
||||
|
||||
@Override |
||||
public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { |
||||
engineCallListener.executionEngineCalled(); |
||||
|
||||
final long startBlockNumber = |
||||
request.getRequiredParameter(0, UnsignedLongParameter.class).getValue(); |
||||
final long count = request.getRequiredParameter(1, UnsignedLongParameter.class).getValue(); |
||||
final Object reqId = request.getRequest().getId(); |
||||
|
||||
traceLambda( |
||||
LOG, |
||||
"{} parameters: start block number {} count {}", |
||||
() -> getName(), |
||||
() -> startBlockNumber, |
||||
() -> count); |
||||
|
||||
if (startBlockNumber < 1 || count < 1) { |
||||
return new JsonRpcErrorResponse(reqId, JsonRpcError.INVALID_PARAMS); |
||||
} |
||||
|
||||
if (count > getMaxRequestBlocks()) { |
||||
return new JsonRpcErrorResponse(reqId, JsonRpcError.INVALID_RANGE_REQUEST_TOO_LARGE); |
||||
} |
||||
|
||||
final Blockchain blockchain = protocolContext.getBlockchain(); |
||||
final long chainHeadBlockNumber = blockchain.getChainHeadBlockNumber(); |
||||
|
||||
// request startBlockNumber is beyond head of chain
|
||||
if (chainHeadBlockNumber < startBlockNumber) { |
||||
// Empty List of payloadBodies
|
||||
return new JsonRpcSuccessResponse(reqId, new EngineGetPayloadBodiesResultV1()); |
||||
} |
||||
|
||||
final long upperBound = startBlockNumber + count; |
||||
|
||||
// if we've received request from blocks beyond the head we exclude those from the query
|
||||
final long endExclusiveBlockNumber = |
||||
chainHeadBlockNumber < upperBound ? chainHeadBlockNumber + 1 : upperBound; |
||||
|
||||
EngineGetPayloadBodiesResultV1 engineGetPayloadBodiesResultV1 = |
||||
blockResultFactory.payloadBodiesCompleteV1( |
||||
LongStream.range(startBlockNumber, endExclusiveBlockNumber) |
||||
.mapToObj( |
||||
blockNumber -> |
||||
blockchain |
||||
.getBlockHashByNumber(blockNumber) |
||||
.flatMap(blockchain::getBlockBody)) |
||||
.collect(Collectors.toList())); |
||||
|
||||
return new JsonRpcSuccessResponse(reqId, engineGetPayloadBodiesResultV1); |
||||
} |
||||
|
||||
protected int getMaxRequestBlocks() { |
||||
return MAX_REQUEST_BLOCKS; |
||||
} |
||||
} |
@ -0,0 +1,79 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; |
||||
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonGetter; |
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder; |
||||
import com.fasterxml.jackson.annotation.JsonValue; |
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
@JsonPropertyOrder({"payloadBodies"}) |
||||
public class EngineGetPayloadBodiesResultV1 { |
||||
|
||||
private final List<PayloadBody> payloadBodies; |
||||
|
||||
public EngineGetPayloadBodiesResultV1() { |
||||
this.payloadBodies = Collections.<PayloadBody>emptyList(); |
||||
} |
||||
|
||||
public EngineGetPayloadBodiesResultV1(final List<PayloadBody> payloadBody) { |
||||
this.payloadBodies = payloadBody; |
||||
} |
||||
|
||||
@JsonValue |
||||
public List<PayloadBody> getPayloadBodies() { |
||||
return payloadBodies; |
||||
} |
||||
|
||||
public static class PayloadBody { |
||||
private final List<String> transactions; |
||||
private final List<WithdrawalParameter> withdrawals; |
||||
|
||||
public PayloadBody(final BlockBody blockBody) { |
||||
this.transactions = |
||||
blockBody.getTransactions().stream() |
||||
.map(TransactionEncoder::encodeOpaqueBytes) |
||||
.map(Bytes::toHexString) |
||||
.collect(Collectors.toList()); |
||||
this.withdrawals = |
||||
blockBody |
||||
.getWithdrawals() |
||||
.map( |
||||
ws -> |
||||
ws.stream() |
||||
.map(WithdrawalParameter::fromWithdrawal) |
||||
.collect(Collectors.toList())) |
||||
.orElse(null); |
||||
} |
||||
|
||||
@JsonGetter(value = "transactions") |
||||
public List<String> getTransactions() { |
||||
return transactions; |
||||
} |
||||
|
||||
@JsonGetter(value = "withdrawals") |
||||
public List<WithdrawalParameter> getWithdrawals() { |
||||
return withdrawals; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,267 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.engine; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.INVALID_RANGE_REQUEST_TOO_LARGE; |
||||
import static org.mockito.Mockito.doReturn; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.crypto.SignatureAlgorithm; |
||||
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.GWei; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; |
||||
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.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.jsonrpc.internal.response.JsonRpcResponseType; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV1; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||
import org.hyperledger.besu.ethereum.core.TransactionTestFixture; |
||||
import org.hyperledger.besu.ethereum.core.Withdrawal; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import io.vertx.core.Vertx; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.apache.tuweni.units.bigints.UInt64; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class EngineGetPayloadBodiesByHashV1Test { |
||||
private EngineGetPayloadBodiesByHashV1 method; |
||||
private static final Vertx vertx = Vertx.vertx(); |
||||
private static final BlockResultFactory blockResultFactory = new BlockResultFactory(); |
||||
@Mock private ProtocolContext protocolContext; |
||||
@Mock private EngineCallListener engineCallListener; |
||||
@Mock private MutableBlockchain blockchain; |
||||
|
||||
@Before |
||||
public void before() { |
||||
when(protocolContext.getBlockchain()).thenReturn(blockchain); |
||||
this.method = |
||||
spy( |
||||
new EngineGetPayloadBodiesByHashV1( |
||||
vertx, protocolContext, blockResultFactory, engineCallListener)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnExpectedMethodName() { |
||||
assertThat(method.getName()).isEqualTo("engine_getPayloadBodiesByHashV1"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnEmptyPayloadBodiesWithEmptyHash() { |
||||
final var resp = resp(new Hash[] {}); |
||||
final EngineGetPayloadBodiesResultV1 result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().isEmpty()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnPayloadForKnownHashes() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash3 = Hash.wrap(Bytes32.random()); |
||||
final BlockBody blockBody1 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
final BlockBody blockBody2 = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
final BlockBody blockBody3 = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(blockBody1)); |
||||
when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(blockBody2)); |
||||
when(blockchain.getBlockBody(blockHash3)).thenReturn(Optional.of(blockBody3)); |
||||
|
||||
final var resp = resp(new Hash[] {blockHash1, blockHash2, blockHash3}); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0).getTransactions().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1).getTransactions().size()).isEqualTo(2); |
||||
assertThat(result.getPayloadBodies().get(2).getTransactions().size()).isEqualTo(3); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnNullForUnknownHashes() { |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash3 = Hash.wrap(Bytes32.random()); |
||||
final var resp = resp(new Hash[] {blockHash1, blockHash2, blockHash3}); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0)).isNull(); |
||||
assertThat(result.getPayloadBodies().get(1)).isNull(); |
||||
assertThat(result.getPayloadBodies().get(2)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnNullForUnknownHashAndPayloadForKnownHash() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash3 = Hash.wrap(Bytes32.random()); |
||||
final BlockBody blockBody1 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
final BlockBody blockBody3 = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(blockBody1)); |
||||
when(blockchain.getBlockBody(blockHash3)).thenReturn(Optional.of(blockBody3)); |
||||
|
||||
final var resp = resp(new Hash[] {blockHash1, blockHash2, blockHash3}); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0).getTransactions().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1)).isNull(); |
||||
assertThat(result.getPayloadBodies().get(2).getTransactions().size()).isEqualTo(3); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnWithdrawalNullWhenBlockIsPreShanghai() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final BlockBody preShanghaiBlockBody = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
|
||||
final BlockBody preShanghaiBlockBody2 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.empty()); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(preShanghaiBlockBody)); |
||||
when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(preShanghaiBlockBody2)); |
||||
|
||||
final var resp = resp(new Hash[] {blockHash1, blockHash2}); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(2); |
||||
assertThat(result.getPayloadBodies().get(0).getTransactions().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0).getWithdrawals()).isNull(); |
||||
assertThat(result.getPayloadBodies().get(1).getTransactions().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1).getWithdrawals()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnWithdrawalsWhenBlockIsPostShanghai() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final Withdrawal withdrawal = |
||||
new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE); |
||||
final Withdrawal withdrawal2 = |
||||
new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x2"), GWei.ONE); |
||||
|
||||
final BlockBody shanghaiBlockBody = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.of(List.of(withdrawal))); |
||||
|
||||
final BlockBody shanghaiBlockBody2 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.of(List.of(withdrawal2))); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(shanghaiBlockBody)); |
||||
when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(shanghaiBlockBody2)); |
||||
|
||||
final var resp = resp(new Hash[] {blockHash1, blockHash2}); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(2); |
||||
assertThat(result.getPayloadBodies().get(0).getTransactions().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0).getWithdrawals().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1).getTransactions().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1).getWithdrawals().size()).isEqualTo(1); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnErrorWhenRequestExceedsPermittedNumberOfBlocks() { |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final Hash[] hashes = new Hash[] {blockHash1, blockHash2}; |
||||
|
||||
doReturn(1).when(method).getMaxRequestBlocks(); |
||||
|
||||
final JsonRpcResponse resp = resp(hashes); |
||||
final var result = fromErrorResp(resp); |
||||
assertThat(result.getCode()).isEqualTo(INVALID_RANGE_REQUEST_TOO_LARGE.getCode()); |
||||
} |
||||
|
||||
private JsonRpcResponse resp(final Hash[] hashes) { |
||||
return method.response( |
||||
new JsonRpcRequestContext( |
||||
new JsonRpcRequest( |
||||
"2.0", |
||||
RpcMethod.ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1.getMethodName(), |
||||
new Object[] {hashes}))); |
||||
} |
||||
|
||||
private EngineGetPayloadBodiesResultV1 fromSuccessResp(final JsonRpcResponse resp) { |
||||
assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); |
||||
return Optional.of(resp) |
||||
.map(JsonRpcSuccessResponse.class::cast) |
||||
.map(JsonRpcSuccessResponse::getResult) |
||||
.map(EngineGetPayloadBodiesResultV1.class::cast) |
||||
.get(); |
||||
} |
||||
|
||||
private JsonRpcError fromErrorResp(final JsonRpcResponse resp) { |
||||
assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); |
||||
return Optional.of(resp) |
||||
.map(JsonRpcErrorResponse.class::cast) |
||||
.map(JsonRpcErrorResponse::getError) |
||||
.get(); |
||||
} |
||||
} |
@ -0,0 +1,352 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.engine; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.INVALID_PARAMS; |
||||
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.INVALID_RANGE_REQUEST_TOO_LARGE; |
||||
import static org.mockito.Mockito.doReturn; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.crypto.SignatureAlgorithm; |
||||
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.GWei; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; |
||||
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.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.jsonrpc.internal.response.JsonRpcResponseType; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV1; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||
import org.hyperledger.besu.ethereum.core.TransactionTestFixture; |
||||
import org.hyperledger.besu.ethereum.core.Withdrawal; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import io.vertx.core.Vertx; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.apache.tuweni.units.bigints.UInt64; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class EngineGetPayloadBodiesByRangeV1Test { |
||||
private EngineGetPayloadBodiesByRangeV1 method; |
||||
private static final Vertx vertx = Vertx.vertx(); |
||||
private static final BlockResultFactory blockResultFactory = new BlockResultFactory(); |
||||
@Mock private ProtocolContext protocolContext; |
||||
@Mock private EngineCallListener engineCallListener; |
||||
@Mock private MutableBlockchain blockchain; |
||||
|
||||
@Before |
||||
public void before() { |
||||
when(protocolContext.getBlockchain()).thenReturn(blockchain); |
||||
this.method = |
||||
spy( |
||||
new EngineGetPayloadBodiesByRangeV1( |
||||
vertx, protocolContext, blockResultFactory, engineCallListener)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnExpectedMethodName() { |
||||
assertThat(method.getName()).isEqualTo("engine_getPayloadBodiesByRangeV1"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnPayloadForKnownNumber() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash3 = Hash.wrap(Bytes32.random()); |
||||
final BlockBody blockBody1 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
final BlockBody blockBody2 = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
final BlockBody blockBody3 = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(130)); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(blockBody1)); |
||||
when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(blockBody2)); |
||||
when(blockchain.getBlockBody(blockHash3)).thenReturn(Optional.of(blockBody3)); |
||||
when(blockchain.getBlockHashByNumber(123)).thenReturn(Optional.of(blockHash1)); |
||||
when(blockchain.getBlockHashByNumber(124)).thenReturn(Optional.of(blockHash2)); |
||||
when(blockchain.getBlockHashByNumber(125)).thenReturn(Optional.of(blockHash3)); |
||||
|
||||
final var resp = resp("0x7b", "0x3"); |
||||
final EngineGetPayloadBodiesResultV1 result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0).getTransactions().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1).getTransactions().size()).isEqualTo(2); |
||||
assertThat(result.getPayloadBodies().get(2).getTransactions().size()).isEqualTo(3); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnNullForUnknownNumber() { |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(130)); |
||||
final var resp = resp("0x7b", "0x3"); |
||||
final EngineGetPayloadBodiesResultV1 result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0)).isNull(); |
||||
assertThat(result.getPayloadBodies().get(1)).isNull(); |
||||
assertThat(result.getPayloadBodies().get(2)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnNullForUnknownNumberAndPayloadForKnownNumber() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash3 = Hash.wrap(Bytes32.random()); |
||||
final BlockBody blockBody1 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
final BlockBody blockBody3 = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(130)); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(blockBody1)); |
||||
when(blockchain.getBlockBody(blockHash3)).thenReturn(Optional.of(blockBody3)); |
||||
when(blockchain.getBlockHashByNumber(123)).thenReturn(Optional.of(blockHash1)); |
||||
when(blockchain.getBlockHashByNumber(125)).thenReturn(Optional.of(blockHash3)); |
||||
|
||||
final var resp = resp("0x7b", "0x3"); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0).getTransactions().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1)).isNull(); |
||||
assertThat(result.getPayloadBodies().get(2).getTransactions().size()).isEqualTo(3); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnNullForWithdrawalsWhenBlockIsPreShanghai() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
|
||||
final BlockBody preShanghaiBlockBody = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList()); |
||||
|
||||
final BlockBody preShanghaiBlockBody2 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.empty()); |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(130)); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(preShanghaiBlockBody)); |
||||
when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(preShanghaiBlockBody2)); |
||||
when(blockchain.getBlockHashByNumber(123)).thenReturn(Optional.of(blockHash1)); |
||||
when(blockchain.getBlockHashByNumber(124)).thenReturn(Optional.of(blockHash2)); |
||||
|
||||
final var resp = resp("0x7b", "0x2"); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(2); |
||||
assertThat(result.getPayloadBodies().get(0).getTransactions().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0).getWithdrawals()).isNull(); |
||||
assertThat(result.getPayloadBodies().get(1).getTransactions().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1).getWithdrawals()).isNull(); |
||||
; |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnWithdrawalsWhenBlockIsPostShanghai() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final Withdrawal withdrawal = |
||||
new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE); |
||||
final Withdrawal withdrawal2 = |
||||
new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x2"), GWei.ONE); |
||||
|
||||
final BlockBody shanghaiBlockBody = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.of(List.of(withdrawal))); |
||||
|
||||
final BlockBody shanghaiBlockBody2 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.of(List.of(withdrawal2))); |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(130)); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(shanghaiBlockBody)); |
||||
when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(shanghaiBlockBody2)); |
||||
when(blockchain.getBlockHashByNumber(123)).thenReturn(Optional.of(blockHash1)); |
||||
when(blockchain.getBlockHashByNumber(124)).thenReturn(Optional.of(blockHash2)); |
||||
|
||||
final var resp = resp("0x7b", "0x2"); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(2); |
||||
assertThat(result.getPayloadBodies().get(0).getTransactions().size()).isEqualTo(3); |
||||
assertThat(result.getPayloadBodies().get(0).getWithdrawals().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1).getTransactions().size()).isEqualTo(1); |
||||
assertThat(result.getPayloadBodies().get(1).getWithdrawals().size()).isEqualTo(1); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotContainTrailingNullForBlocksPastTheCurrentHead() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Withdrawal withdrawal = |
||||
new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE); |
||||
|
||||
final BlockBody shanghaiBlockBody = |
||||
new BlockBody( |
||||
List.of( |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair()), |
||||
new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.of(List.of(withdrawal))); |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(123)); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(shanghaiBlockBody)); |
||||
when(blockchain.getBlockHashByNumber(123)).thenReturn(Optional.of(blockHash1)); |
||||
|
||||
final var resp = resp("0x7b", "0x3"); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(1); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnUpUntilHeadWhenStartBlockPlusCountEqualsHeadNumber() { |
||||
final SignatureAlgorithm sig = SignatureAlgorithmFactory.getInstance(); |
||||
final Hash blockHash1 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash2 = Hash.wrap(Bytes32.random()); |
||||
final Hash blockHash3 = Hash.wrap(Bytes32.random()); |
||||
final Withdrawal withdrawal = |
||||
new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE); |
||||
|
||||
final BlockBody shanghaiBlockBody = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.of(List.of(withdrawal))); |
||||
final BlockBody shanghaiBlockBody2 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.of(List.of(withdrawal))); |
||||
final BlockBody shanghaiBlockBody3 = |
||||
new BlockBody( |
||||
List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), |
||||
Collections.emptyList(), |
||||
Optional.of(List.of(withdrawal))); |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(125)); |
||||
when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(shanghaiBlockBody)); |
||||
when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(shanghaiBlockBody2)); |
||||
when(blockchain.getBlockBody(blockHash3)).thenReturn(Optional.of(shanghaiBlockBody3)); |
||||
when(blockchain.getBlockHashByNumber(123)).thenReturn(Optional.of(blockHash1)); |
||||
when(blockchain.getBlockHashByNumber(124)).thenReturn(Optional.of(blockHash2)); |
||||
when(blockchain.getBlockHashByNumber(125)).thenReturn(Optional.of(blockHash3)); |
||||
|
||||
final var resp = resp("0x7b", "0x3"); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies().size()).isEqualTo(3); |
||||
} |
||||
|
||||
@Test |
||||
public void ShouldReturnEmptyPayloadForRequestsPastCurrentHead() { |
||||
|
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(123)); |
||||
final JsonRpcResponse resp = resp("0x7d", "0x3"); |
||||
final var result = fromSuccessResp(resp); |
||||
assertThat(result.getPayloadBodies()).isEqualTo(Collections.EMPTY_LIST); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnErrorWhenRequestExceedsPermittedNumberOfBlocks() { |
||||
doReturn(3).when(method).getMaxRequestBlocks(); |
||||
final JsonRpcResponse resp = resp("0x539", "0x4"); |
||||
final var result = fromErrorResp(resp); |
||||
assertThat(result.getCode()).isEqualTo(INVALID_RANGE_REQUEST_TOO_LARGE.getCode()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnInvalidParamsIfStartIsZero() { |
||||
final JsonRpcResponse resp = resp("0x0", "0x539"); |
||||
final var result = fromErrorResp(resp); |
||||
assertThat(result.getCode()).isEqualTo(INVALID_PARAMS.getCode()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnInvalidParamsIfCountIsZero() { |
||||
final JsonRpcResponse resp = resp("0x539", "0x0"); |
||||
final var result = fromErrorResp(resp); |
||||
assertThat(result.getCode()).isEqualTo(INVALID_PARAMS.getCode()); |
||||
} |
||||
|
||||
private JsonRpcResponse resp(final String startBlockNumber, final String range) { |
||||
return method.response( |
||||
new JsonRpcRequestContext( |
||||
new JsonRpcRequest( |
||||
"2.0", |
||||
RpcMethod.ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1.getMethodName(), |
||||
new Object[] {startBlockNumber, range}))); |
||||
} |
||||
|
||||
private EngineGetPayloadBodiesResultV1 fromSuccessResp(final JsonRpcResponse resp) { |
||||
assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); |
||||
return Optional.of(resp) |
||||
.map(JsonRpcSuccessResponse.class::cast) |
||||
.map(JsonRpcSuccessResponse::getResult) |
||||
.map(EngineGetPayloadBodiesResultV1.class::cast) |
||||
.get(); |
||||
} |
||||
|
||||
private JsonRpcError fromErrorResp(final JsonRpcResponse resp) { |
||||
assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); |
||||
return Optional.of(resp) |
||||
.map(JsonRpcErrorResponse.class::cast) |
||||
.map(JsonRpcErrorResponse::getError) |
||||
.get(); |
||||
} |
||||
} |
@ -1,172 +0,0 @@ |
||||
/* |
||||
* 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.eth.sync.worldstate; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.eth.EthProtocolVersion; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext; |
||||
import org.hyperledger.besu.ethereum.eth.manager.snap.RetryingGetTrieNodeFromPeerTask; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.RetryingGetNodeDataFromPeerTask; |
||||
import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; |
||||
import org.hyperledger.besu.ethereum.trie.CompactEncoding; |
||||
import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.ExecutionException; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.TimeoutException; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import com.google.common.cache.Cache; |
||||
import com.google.common.cache.CacheBuilder; |
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
/** This class is used to retrieve missing nodes in the trie by querying the peers */ |
||||
public class WorldStatePeerTrieNodeFinder implements PeerTrieNodeFinder { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WorldStatePeerTrieNodeFinder.class); |
||||
|
||||
private final Cache<Bytes32, Bytes> foundNodes = |
||||
CacheBuilder.newBuilder().maximumSize(10_000).expireAfterWrite(5, TimeUnit.MINUTES).build(); |
||||
|
||||
private static final long TIMEOUT_SECONDS = 1; |
||||
|
||||
final ProtocolManager protocolManager; |
||||
final EthContext ethContext; |
||||
final Blockchain blockchain; |
||||
final MetricsSystem metricsSystem; |
||||
|
||||
public WorldStatePeerTrieNodeFinder( |
||||
final EthContext ethContext, |
||||
final ProtocolManager protocolManager, |
||||
final Blockchain blockchain, |
||||
final MetricsSystem metricsSystem) { |
||||
this.ethContext = ethContext; |
||||
this.protocolManager = protocolManager; |
||||
this.blockchain = blockchain; |
||||
this.metricsSystem = metricsSystem; |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Bytes> getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { |
||||
Optional<Bytes> cachedValue = Optional.ofNullable(foundNodes.getIfPresent(nodeHash)); |
||||
if (cachedValue.isPresent()) { |
||||
return cachedValue; |
||||
} |
||||
final Optional<Bytes> response = |
||||
findByGetNodeData(Hash.wrap(nodeHash)) |
||||
.or(() -> findByGetTrieNodeData(Hash.wrap(nodeHash), Optional.empty(), location)); |
||||
response.ifPresent( |
||||
bytes -> { |
||||
LOG.debug( |
||||
"Fixed missing account state trie node for location {} and hash {}", |
||||
location, |
||||
nodeHash); |
||||
foundNodes.put(nodeHash, bytes); |
||||
}); |
||||
return response; |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Bytes> getAccountStorageTrieNode( |
||||
final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { |
||||
Optional<Bytes> cachedValue = Optional.ofNullable(foundNodes.getIfPresent(nodeHash)); |
||||
if (cachedValue.isPresent()) { |
||||
return cachedValue; |
||||
} |
||||
final Optional<Bytes> response = |
||||
// Call findByGetNodeData only if protocol version < eth 67
|
||||
(protocolManager.getHighestProtocolVersion() < EthProtocolVersion.V67 |
||||
? findByGetNodeData(Hash.wrap(nodeHash)) |
||||
: Optional.<Bytes>empty()) |
||||
.or( |
||||
() -> |
||||
findByGetTrieNodeData(Hash.wrap(nodeHash), Optional.of(accountHash), location)); |
||||
response.ifPresent( |
||||
bytes -> { |
||||
LOG.debug( |
||||
"Fixed missing storage state trie node for location {} and hash {}", |
||||
location, |
||||
nodeHash); |
||||
foundNodes.put(nodeHash, bytes); |
||||
}); |
||||
return response; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
public Optional<Bytes> findByGetNodeData(final Hash nodeHash) { |
||||
if (protocolManager.getHighestProtocolVersion() >= EthProtocolVersion.V67) { |
||||
return Optional.empty(); |
||||
} |
||||
final BlockHeader chainHead = blockchain.getChainHeadHeader(); |
||||
final RetryingGetNodeDataFromPeerTask retryingGetNodeDataFromPeerTask = |
||||
RetryingGetNodeDataFromPeerTask.forHashes( |
||||
ethContext, List.of(nodeHash), chainHead.getNumber(), metricsSystem); |
||||
try { |
||||
final Map<Hash, Bytes> response = |
||||
retryingGetNodeDataFromPeerTask.run().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); |
||||
if (response.containsKey(nodeHash)) { |
||||
LOG.debug("Found node {} with getNodeData request", nodeHash); |
||||
return Optional.of(response.get(nodeHash)); |
||||
} else { |
||||
LOG.debug("Found invalid node {} with getNodeData request", nodeHash); |
||||
} |
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) { |
||||
LOG.debug("Error when trying to find node {} with getNodeData request", nodeHash); |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
public Optional<Bytes> findByGetTrieNodeData( |
||||
final Hash nodeHash, final Optional<Bytes32> accountHash, final Bytes location) { |
||||
final BlockHeader chainHead = blockchain.getChainHeadHeader(); |
||||
final Map<Bytes, List<Bytes>> request = new HashMap<>(); |
||||
if (accountHash.isPresent()) { |
||||
request.put(accountHash.get(), List.of(CompactEncoding.encode(location))); |
||||
} else { |
||||
request.put(CompactEncoding.encode(location), new ArrayList<>()); |
||||
} |
||||
final Bytes path = CompactEncoding.encode(location); |
||||
final EthTask<Map<Bytes, Bytes>> getTrieNodeFromPeerTask = |
||||
RetryingGetTrieNodeFromPeerTask.forTrieNodes(ethContext, request, chainHead, metricsSystem); |
||||
try { |
||||
final Map<Bytes, Bytes> response = |
||||
getTrieNodeFromPeerTask.run().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); |
||||
final Bytes nodeValue = |
||||
response.get(Bytes.concatenate(accountHash.map(Bytes::wrap).orElse(Bytes.EMPTY), path)); |
||||
if (nodeValue != null && Hash.hash(nodeValue).equals(nodeHash)) { |
||||
LOG.debug("Found node {} with getTrieNode request", nodeHash); |
||||
return Optional.of(nodeValue); |
||||
} else { |
||||
LOG.debug("Found invalid node {} with getTrieNode request", nodeHash); |
||||
} |
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) { |
||||
LOG.debug("Error when trying to find node {} with getTrieNode request", nodeHash); |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue