mirror of https://github.com/hyperledger/besu
Signed-off-by: stefan.pingel@consensys.net <stefan.pingel@consensys.net>pull/7806/head
parent
16bcb1a5b7
commit
dc1f3bdf9f
@ -0,0 +1,154 @@ |
|||||||
|
/* |
||||||
|
* 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.manager.peertask.task; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.core.Block; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||||
|
import org.hyperledger.besu.ethereum.eth.EthProtocol; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.InvalidPeerTaskResponseException; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.PeerTask; |
||||||
|
import org.hyperledger.besu.ethereum.eth.messages.BlockBodiesMessage; |
||||||
|
import org.hyperledger.besu.ethereum.eth.messages.GetBlockBodiesMessage; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.BodyValidation; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.function.Predicate; |
||||||
|
import java.util.function.Supplier; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
public class GetBodiesFromPeerTask implements PeerTask<List<Block>> { |
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(GetBodiesFromPeerTask.class); |
||||||
|
|
||||||
|
private final List<BlockHeader> blockHeaders; |
||||||
|
private final Supplier<ProtocolSpec> currentProtocolSpecSupplier; |
||||||
|
private final ProtocolSchedule protocolSchedule; |
||||||
|
|
||||||
|
private final long requiredBlockchainHeight; |
||||||
|
private final List<Block> blocks = new ArrayList<>(); |
||||||
|
|
||||||
|
public GetBodiesFromPeerTask( |
||||||
|
final List<BlockHeader> blockHeaders, |
||||||
|
final Supplier<ProtocolSpec> currentProtocolSpecSupplier, |
||||||
|
final ProtocolSchedule protocolSchedule) { |
||||||
|
|
||||||
|
this.blockHeaders = blockHeaders; |
||||||
|
this.currentProtocolSpecSupplier = currentProtocolSpecSupplier; |
||||||
|
this.protocolSchedule = protocolSchedule; |
||||||
|
|
||||||
|
this.requiredBlockchainHeight = |
||||||
|
blockHeaders.stream() |
||||||
|
.mapToLong(BlockHeader::getNumber) |
||||||
|
.max() |
||||||
|
.orElse(BlockHeader.GENESIS_BLOCK_NUMBER); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public SubProtocol getSubProtocol() { |
||||||
|
return EthProtocol.get(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public MessageData getRequestMessage() { |
||||||
|
return GetBlockBodiesMessage.create( |
||||||
|
blockHeaders.stream().map(BlockHeader::getBlockHash).toList()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<Block> parseResponse(final MessageData messageData) |
||||||
|
throws InvalidPeerTaskResponseException { |
||||||
|
// Blocks returned by this method are in the same order as the headers, but might not be
|
||||||
|
// complete
|
||||||
|
if (messageData == null) { |
||||||
|
throw new InvalidPeerTaskResponseException(); |
||||||
|
} |
||||||
|
final BlockBodiesMessage blocksMessage = BlockBodiesMessage.readFrom(messageData); |
||||||
|
final List<BlockBody> blockBodies = blocksMessage.bodies(protocolSchedule); |
||||||
|
if (blockBodies.isEmpty() || blockBodies.size() > blockHeaders.size()) { |
||||||
|
throw new InvalidPeerTaskResponseException(); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < blockBodies.size(); i++) { |
||||||
|
final BlockBody blockBody = blockBodies.get(i); |
||||||
|
final BlockHeader blockHeader = blockHeaders.get(i); |
||||||
|
if (!blockBodyMatchesBlockHeader(blockBody, blockHeader)) { |
||||||
|
LOG.atDebug().setMessage("Received block body does not match block header").log(); |
||||||
|
throw new InvalidPeerTaskResponseException(); |
||||||
|
} |
||||||
|
|
||||||
|
blocks.add(new Block(blockHeader, blockBody)); |
||||||
|
} |
||||||
|
return blocks; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean blockBodyMatchesBlockHeader( |
||||||
|
final BlockBody blockBody, final BlockHeader blockHeader) { |
||||||
|
// this method validates that the block body matches the block header by calculating the roots
|
||||||
|
// of the block body
|
||||||
|
// and comparing them to the roots in the block header
|
||||||
|
if (!BodyValidation.transactionsRoot(blockBody.getTransactions()) |
||||||
|
.equals(blockHeader.getTransactionsRoot())) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (!BodyValidation.ommersHash(blockBody.getOmmers()).equals(blockHeader.getOmmersHash())) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (blockBody.getWithdrawals().isPresent()) { |
||||||
|
if (blockHeader.getWithdrawalsRoot().isEmpty()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (!BodyValidation.withdrawalsRoot(blockBody.getWithdrawals().get()) |
||||||
|
.equals(blockHeader.getWithdrawalsRoot().get())) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} else if (blockHeader.getWithdrawalsRoot().isPresent()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (blockBody.getRequests().isPresent()) { |
||||||
|
if (blockHeader.getRequestsRoot().isEmpty()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (!BodyValidation.requestsRoot(blockBody.getRequests().get()) |
||||||
|
.equals(blockHeader.getRequestsRoot().get())) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} else if (blockHeader.getRequestsRoot().isPresent()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Predicate<EthPeer> getPeerRequirementFilter() { |
||||||
|
return (ethPeer) -> |
||||||
|
ethPeer.getProtocolName().equals(getSubProtocol().getName()) |
||||||
|
&& (currentProtocolSpecSupplier.get().isPoS() |
||||||
|
|| ethPeer.chainState().getEstimatedHeight() >= requiredBlockchainHeight); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isSuccess(final List<Block> result) { |
||||||
|
return !result.isEmpty(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
/* |
||||||
|
* 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.eth.sync.tasks; |
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.core.Block; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.PeerTaskExecutor; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.PeerTaskExecutorResponseCode; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.PeerTaskExecutorResult; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.task.GetBodiesFromPeerTask; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
/** |
||||||
|
* Given a set of headers, "completes" them by repeatedly requesting additional data (bodies) needed |
||||||
|
* to create the blocks that correspond to the supplied headers. |
||||||
|
*/ |
||||||
|
public class CompleteBlocksWithPeerTask { |
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(CompleteBlocksWithPeerTask.class); |
||||||
|
|
||||||
|
private final ProtocolSchedule protocolSchedule; |
||||||
|
private final List<BlockHeader> headersToGet = new ArrayList<>(); |
||||||
|
private final PeerTaskExecutor peerTaskExecutor; |
||||||
|
|
||||||
|
private final Block[] result; |
||||||
|
private final int resultSize; |
||||||
|
private int nextIndex = 0; |
||||||
|
private int remainingBlocks = 0; |
||||||
|
|
||||||
|
public CompleteBlocksWithPeerTask( |
||||||
|
final ProtocolSchedule protocolSchedule, |
||||||
|
final List<BlockHeader> headers, |
||||||
|
final PeerTaskExecutor peerTaskExecutor) { |
||||||
|
checkArgument(!headers.isEmpty(), "Must supply a non-empty headers list"); |
||||||
|
this.protocolSchedule = protocolSchedule; |
||||||
|
this.peerTaskExecutor = peerTaskExecutor; |
||||||
|
|
||||||
|
resultSize = headers.size(); |
||||||
|
result = new Block[resultSize]; |
||||||
|
remainingBlocks = resultSize; |
||||||
|
|
||||||
|
for (int i = 0; i < resultSize; i++) { |
||||||
|
final BlockHeader header = headers.get(i); |
||||||
|
if (BlockHeader.hasEmptyBlock(header)) { |
||||||
|
final Block emptyBlock = |
||||||
|
new Block(header, createEmptyBodyBasedOnProtocolSchedule(protocolSchedule, header)); |
||||||
|
result[i] = emptyBlock; |
||||||
|
remainingBlocks--; |
||||||
|
} else { |
||||||
|
headersToGet.add(header); |
||||||
|
} |
||||||
|
} |
||||||
|
this.nextIndex = findNextIndex(0); |
||||||
|
} |
||||||
|
|
||||||
|
private BlockBody createEmptyBodyBasedOnProtocolSchedule( |
||||||
|
final ProtocolSchedule protocolSchedule, final BlockHeader header) { |
||||||
|
return new BlockBody( |
||||||
|
Collections.emptyList(), |
||||||
|
Collections.emptyList(), |
||||||
|
isWithdrawalsEnabled(protocolSchedule, header) |
||||||
|
? Optional.of(Collections.emptyList()) |
||||||
|
: Optional.empty(), |
||||||
|
Optional.empty()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isWithdrawalsEnabled( |
||||||
|
final ProtocolSchedule protocolSchedule, final BlockHeader header) { |
||||||
|
return protocolSchedule.getByBlockHeader(header).getWithdrawalsProcessor().isPresent(); |
||||||
|
} |
||||||
|
|
||||||
|
public List<Block> getBlocks() { |
||||||
|
while (remainingBlocks > 0) { |
||||||
|
LOG.atDebug() |
||||||
|
.setMessage("Requesting {} bodies from peer") |
||||||
|
.addArgument(headersToGet.size()) |
||||||
|
.log(); |
||||||
|
final GetBodiesFromPeerTask task = |
||||||
|
new GetBodiesFromPeerTask( |
||||||
|
headersToGet, |
||||||
|
() -> protocolSchedule.getByBlockHeader(headersToGet.getLast()), |
||||||
|
protocolSchedule); |
||||||
|
final PeerTaskExecutorResult<List<Block>> executionResult = peerTaskExecutor.execute(task); |
||||||
|
if (executionResult.responseCode() == PeerTaskExecutorResponseCode.SUCCESS |
||||||
|
&& executionResult.result().isPresent()) { |
||||||
|
final List<Block> blockList = executionResult.result().get(); |
||||||
|
LOG.atDebug() |
||||||
|
.setMessage("Received {} bodies out of {} from peer") |
||||||
|
.addArgument(blockList.size()) |
||||||
|
.addArgument(headersToGet.size()) |
||||||
|
.log(); |
||||||
|
blockList.forEach( |
||||||
|
block -> { |
||||||
|
remainingBlocks--; |
||||||
|
result[nextIndex] = block; |
||||||
|
nextIndex = findNextIndex(nextIndex + 1); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
return List.of(result); |
||||||
|
} |
||||||
|
|
||||||
|
private int findNextIndex(final int startIndex) { |
||||||
|
for (int i = startIndex; i < resultSize; i++) { |
||||||
|
if (result[i] == null) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; // This only happens when we have finished processing all headers
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,199 @@ |
|||||||
|
/* |
||||||
|
* 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.manager.peertask.task; |
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.Hash; |
||||||
|
import org.hyperledger.besu.ethereum.core.Block; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; |
||||||
|
import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder; |
||||||
|
import org.hyperledger.besu.ethereum.eth.EthProtocol; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.ChainState; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.InvalidPeerTaskResponseException; |
||||||
|
import org.hyperledger.besu.ethereum.eth.messages.BlockBodiesMessage; |
||||||
|
import org.hyperledger.besu.ethereum.eth.messages.EthPV62; |
||||||
|
import org.hyperledger.besu.ethereum.eth.messages.GetBlockBodiesMessage; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.BodyValidation; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; |
||||||
|
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.junit.jupiter.api.Assertions; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.mockito.Mockito; |
||||||
|
|
||||||
|
public class GetBodiesFromPeerTaskTest { |
||||||
|
|
||||||
|
private static final String FRONTIER_TX_RLP = |
||||||
|
"0xf901fc8032830138808080b901ae60056013565b6101918061001d6000396000f35b3360008190555056006001600060e060020a6000350480630a874df61461003a57806341c0e1b514610058578063a02b161e14610066578063dbbdf0831461007757005b610045600435610149565b80600160a060020a031660005260206000f35b610060610161565b60006000f35b6100716004356100d4565b60006000f35b61008560043560243561008b565b60006000f35b600054600160a060020a031632600160a060020a031614156100ac576100b1565b6100d0565b8060018360005260205260406000208190555081600060005260206000a15b5050565b600054600160a060020a031633600160a060020a031614158015610118575033600160a060020a0316600182600052602052604060002054600160a060020a031614155b61012157610126565b610146565b600060018260005260205260406000208190555080600060005260206000a15b50565b60006001826000526020526040600020549050919050565b600054600160a060020a031633600160a060020a0316146101815761018f565b600054600160a060020a0316ff5b561ca0c5689ed1ad124753d54576dfb4b571465a41900a1dff4058d8adf16f752013d0a01221cbd70ec28c94a3b55ec771bcbc70778d6ee0b51ca7ea9514594c861b1884"; |
||||||
|
|
||||||
|
private static final Transaction TX = |
||||||
|
TransactionDecoder.decodeRLP( |
||||||
|
new BytesValueRLPInput(Bytes.fromHexString(FRONTIER_TX_RLP), false), |
||||||
|
EncodingContext.BLOCK_BODY); |
||||||
|
public static final List<Transaction> TRANSACTION_LIST = List.of(TX); |
||||||
|
public static final BlockBody BLOCK_BODY = |
||||||
|
new BlockBody(TRANSACTION_LIST, Collections.emptyList(), Optional.empty(), Optional.empty()); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testGetSubProtocol() { |
||||||
|
GetBodiesFromPeerTask task = new GetBodiesFromPeerTask(Collections.emptyList(), null, null); |
||||||
|
Assertions.assertEquals(EthProtocol.get(), task.getSubProtocol()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testGetRequestMessage() { |
||||||
|
GetBodiesFromPeerTask task = |
||||||
|
new GetBodiesFromPeerTask( |
||||||
|
List.of(mockBlockHeader(1), mockBlockHeader(2), mockBlockHeader(3)), null, null); |
||||||
|
|
||||||
|
MessageData messageData = task.getRequestMessage(); |
||||||
|
GetBlockBodiesMessage getBlockBodiesMessage = GetBlockBodiesMessage.readFrom(messageData); |
||||||
|
|
||||||
|
Assertions.assertEquals(EthPV62.GET_BLOCK_BODIES, getBlockBodiesMessage.getCode()); |
||||||
|
Iterable<Hash> hashesInMessage = getBlockBodiesMessage.hashes(); |
||||||
|
List<Hash> expectedHashes = |
||||||
|
List.of( |
||||||
|
Hash.fromHexString(StringUtils.repeat("00", 31) + "11"), |
||||||
|
Hash.fromHexString(StringUtils.repeat("00", 31) + "21"), |
||||||
|
Hash.fromHexString(StringUtils.repeat("00", 31) + "31")); |
||||||
|
List<Hash> actualHashes = new ArrayList<>(); |
||||||
|
hashesInMessage.forEach(actualHashes::add); |
||||||
|
|
||||||
|
Assertions.assertEquals(3, actualHashes.size()); |
||||||
|
Assertions.assertEquals( |
||||||
|
expectedHashes.stream().sorted().toList(), actualHashes.stream().sorted().toList()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testParseResponseWithNullResponseMessage() { |
||||||
|
GetBodiesFromPeerTask task = new GetBodiesFromPeerTask(Collections.emptyList(), null, null); |
||||||
|
Assertions.assertThrows(InvalidPeerTaskResponseException.class, () -> task.parseResponse(null)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testParseResponseForInvalidResponse() { |
||||||
|
GetBodiesFromPeerTask task = new GetBodiesFromPeerTask(List.of(mockBlockHeader(1)), null, null); |
||||||
|
// body does not match header
|
||||||
|
BlockBodiesMessage bodiesMessage = BlockBodiesMessage.create(List.of(BLOCK_BODY)); |
||||||
|
|
||||||
|
Assertions.assertThrows( |
||||||
|
InvalidPeerTaskResponseException.class, () -> task.parseResponse(bodiesMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testParseResponse() throws InvalidPeerTaskResponseException { |
||||||
|
final BlockHeader nonEmptyBlockHeaderMock = |
||||||
|
getNonEmptyBlockHeaderMock(BodyValidation.transactionsRoot(TRANSACTION_LIST).toString()); |
||||||
|
|
||||||
|
GetBodiesFromPeerTask task = |
||||||
|
new GetBodiesFromPeerTask(List.of(nonEmptyBlockHeaderMock), null, null); |
||||||
|
|
||||||
|
final BlockBodiesMessage blockBodiesMessage = BlockBodiesMessage.create(List.of(BLOCK_BODY)); |
||||||
|
|
||||||
|
List<Block> result = task.parseResponse(blockBodiesMessage); |
||||||
|
|
||||||
|
assertThat(result.size()).isEqualTo(1); |
||||||
|
assertThat(result.getFirst().getBody().getTransactions()).isEqualTo(TRANSACTION_LIST); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testGetPeerRequirementFilter() { |
||||||
|
BlockHeader blockHeader1 = mockBlockHeader(1); |
||||||
|
BlockHeader blockHeader2 = mockBlockHeader(2); |
||||||
|
BlockHeader blockHeader3 = mockBlockHeader(3); |
||||||
|
|
||||||
|
ProtocolSpec protocolSpec = Mockito.mock(ProtocolSpec.class); |
||||||
|
Mockito.when(protocolSpec.isPoS()).thenReturn(false); |
||||||
|
|
||||||
|
GetBodiesFromPeerTask task = |
||||||
|
new GetBodiesFromPeerTask( |
||||||
|
List.of(blockHeader1, blockHeader2, blockHeader3), () -> protocolSpec, null); |
||||||
|
|
||||||
|
EthPeer failForIncorrectProtocol = mockPeer("incorrectProtocol", 5); |
||||||
|
EthPeer failForShortChainHeight = mockPeer("incorrectProtocol", 1); |
||||||
|
EthPeer successfulCandidate = mockPeer(EthProtocol.NAME, 5); |
||||||
|
|
||||||
|
Assertions.assertFalse(task.getPeerRequirementFilter().test(failForIncorrectProtocol)); |
||||||
|
Assertions.assertFalse(task.getPeerRequirementFilter().test(failForShortChainHeight)); |
||||||
|
Assertions.assertTrue(task.getPeerRequirementFilter().test(successfulCandidate)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testIsSuccessForPartialSuccess() { |
||||||
|
GetBodiesFromPeerTask task = new GetBodiesFromPeerTask(Collections.emptyList(), null, null); |
||||||
|
|
||||||
|
Assertions.assertFalse(task.isSuccess(Collections.emptyList())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testIsSuccessForFullSuccess() { |
||||||
|
GetBodiesFromPeerTask task = new GetBodiesFromPeerTask(Collections.emptyList(), null, null); |
||||||
|
|
||||||
|
final List<Block> blockHeaders = List.of(mock(Block.class)); |
||||||
|
|
||||||
|
Assertions.assertTrue(task.isSuccess(blockHeaders)); |
||||||
|
} |
||||||
|
|
||||||
|
private BlockHeader mockBlockHeader(final long blockNumber) { |
||||||
|
BlockHeader blockHeader = Mockito.mock(BlockHeader.class); |
||||||
|
Mockito.when(blockHeader.getNumber()).thenReturn(blockNumber); |
||||||
|
// second to last hex digit indicates the blockNumber, last hex digit indicates the usage of the
|
||||||
|
// hash
|
||||||
|
Mockito.when(blockHeader.getHash()) |
||||||
|
.thenReturn(Hash.fromHexString(StringUtils.repeat("00", 31) + blockNumber + "1")); |
||||||
|
Mockito.when(blockHeader.getBlockHash()) |
||||||
|
.thenReturn(Hash.fromHexString(StringUtils.repeat("00", 31) + blockNumber + "1")); |
||||||
|
Mockito.when(blockHeader.getReceiptsRoot()) |
||||||
|
.thenReturn(Hash.fromHexString(StringUtils.repeat("00", 31) + blockNumber + "2")); |
||||||
|
|
||||||
|
return blockHeader; |
||||||
|
} |
||||||
|
|
||||||
|
private static BlockHeader getNonEmptyBlockHeaderMock(final String transactionsRootHexString) { |
||||||
|
final BlockHeader blockHeader = mock(BlockHeader.class); |
||||||
|
when(blockHeader.getTransactionsRoot()) |
||||||
|
.thenReturn(Hash.fromHexStringLenient(transactionsRootHexString)); |
||||||
|
when(blockHeader.getOmmersHash()).thenReturn(Hash.EMPTY_LIST_HASH); |
||||||
|
when(blockHeader.getWithdrawalsRoot()).thenReturn(Optional.empty()); |
||||||
|
when(blockHeader.getRequestsRoot()).thenReturn(Optional.empty()); |
||||||
|
return blockHeader; |
||||||
|
} |
||||||
|
|
||||||
|
private EthPeer mockPeer(final String protocol, final long chainHeight) { |
||||||
|
EthPeer ethPeer = Mockito.mock(EthPeer.class); |
||||||
|
ChainState chainState = Mockito.mock(ChainState.class); |
||||||
|
|
||||||
|
Mockito.when(ethPeer.getProtocolName()).thenReturn(protocol); |
||||||
|
Mockito.when(ethPeer.chainState()).thenReturn(chainState); |
||||||
|
Mockito.when(chainState.getEstimatedHeight()).thenReturn(chainHeight); |
||||||
|
|
||||||
|
return ethPeer; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,251 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to 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.tasks; |
||||||
|
|
||||||
|
import static java.util.Arrays.asList; |
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; |
||||||
|
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; |
||||||
|
import static org.mockito.ArgumentMatchers.any; |
||||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.Hash; |
||||||
|
import org.hyperledger.besu.ethereum.core.Block; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.PeerTaskExecutor; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.PeerTaskExecutorResponseCode; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.peertask.PeerTaskExecutorResult; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.mockito.Mockito; |
||||||
|
|
||||||
|
public class CompleteBlocksWithPeerTaskTest { |
||||||
|
|
||||||
|
@BeforeAll |
||||||
|
public static void setUp() {} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldFailWhenEmptyHeaders() { |
||||||
|
assertThatThrownBy(() -> new CompleteBlocksWithPeerTask(null, Collections.emptyList(), null)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||||
|
.hasMessage("Must supply a non-empty headers list"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnEmptyBlock() { |
||||||
|
final ProtocolSchedule protocolSchedule = getProtocolScheduleMock(); |
||||||
|
final BlockHeader blockHeader = getEmptyBlockHeaderMock(); |
||||||
|
final PeerTaskExecutor peerTaskExecutor = mock(PeerTaskExecutor.class); |
||||||
|
|
||||||
|
CompleteBlocksWithPeerTask completeBlocksWithPeerTask = |
||||||
|
new CompleteBlocksWithPeerTask(protocolSchedule, List.of(blockHeader), peerTaskExecutor); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks()).isNotEmpty(); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().size()).isEqualTo(1); |
||||||
|
assertThat(BlockHeader.hasEmptyBlock(completeBlocksWithPeerTask.getBlocks().get(0).getHeader())) |
||||||
|
.isTrue(); |
||||||
|
|
||||||
|
verify(peerTaskExecutor, Mockito.never()).execute(any()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldCreateWithdrawalsAwareEmptyBlock_whenWithdrawalsAreEnabled() { |
||||||
|
final ProtocolSchedule mockProtocolSchedule = Mockito.mock(ProtocolSchedule.class); |
||||||
|
final ProtocolSpec mockParisSpec = Mockito.mock(ProtocolSpec.class); |
||||||
|
final ProtocolSpec mockShanghaiSpec = Mockito.mock(ProtocolSpec.class); |
||||||
|
final WithdrawalsProcessor mockWithdrawalsProcessor = Mockito.mock(WithdrawalsProcessor.class); |
||||||
|
|
||||||
|
final BlockHeader header1 = |
||||||
|
new BlockHeaderTestFixture().number(1).withdrawalsRoot(null).buildHeader(); |
||||||
|
final BlockHeader header2 = |
||||||
|
new BlockHeaderTestFixture().number(2).withdrawalsRoot(Hash.EMPTY_TRIE_HASH).buildHeader(); |
||||||
|
|
||||||
|
when(mockProtocolSchedule.getByBlockHeader((eq(header1)))).thenReturn(mockParisSpec); |
||||||
|
when(mockParisSpec.getWithdrawalsProcessor()).thenReturn(Optional.empty()); |
||||||
|
when(mockProtocolSchedule.getByBlockHeader((eq(header2)))).thenReturn(mockShanghaiSpec); |
||||||
|
when(mockShanghaiSpec.getWithdrawalsProcessor()) |
||||||
|
.thenReturn(Optional.of(mockWithdrawalsProcessor)); |
||||||
|
|
||||||
|
final List<Block> expectedBlocks = getExpectedBlocks(header1, header2); |
||||||
|
|
||||||
|
final PeerTaskExecutor peerTaskExecutor = mock(PeerTaskExecutor.class); |
||||||
|
when(peerTaskExecutor.execute(any())) |
||||||
|
.thenReturn( |
||||||
|
new PeerTaskExecutorResult<>( |
||||||
|
Optional.of(expectedBlocks), PeerTaskExecutorResponseCode.SUCCESS)); |
||||||
|
|
||||||
|
final CompleteBlocksWithPeerTask task = |
||||||
|
new CompleteBlocksWithPeerTask( |
||||||
|
mockProtocolSchedule, asList(header1, header2), peerTaskExecutor); |
||||||
|
final List<Block> blocks = task.getBlocks(); |
||||||
|
|
||||||
|
assertThat(blocks).isEqualTo(expectedBlocks); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnNonEmptyBlock() { |
||||||
|
final Block block = mock(Block.class); |
||||||
|
final ProtocolSchedule protocolSchedule = getProtocolScheduleMock(); |
||||||
|
final PeerTaskExecutor peerTaskExecutor = mock(PeerTaskExecutor.class); |
||||||
|
final BlockHeader nonEmptyBlockHeaderMock = getNonEmptyBlockHeaderMock("0x01", "0x02"); |
||||||
|
when(peerTaskExecutor.execute(any())) |
||||||
|
.thenReturn( |
||||||
|
new PeerTaskExecutorResult<>( |
||||||
|
Optional.of(List.of(block)), PeerTaskExecutorResponseCode.SUCCESS)); |
||||||
|
|
||||||
|
CompleteBlocksWithPeerTask completeBlocksWithPeerTask = |
||||||
|
new CompleteBlocksWithPeerTask( |
||||||
|
protocolSchedule, List.of(nonEmptyBlockHeaderMock), peerTaskExecutor); |
||||||
|
|
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks()).isNotEmpty(); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().size()).isEqualTo(1); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().get(0)).isEqualTo(block); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnBlocksInRightOrderWhenEmptyAndNonEmptyBlocksRequested() { |
||||||
|
final Block block1 = mock(Block.class); |
||||||
|
final Block block3 = mock(Block.class); |
||||||
|
final BlockHeader emptyBlockHeaderMock = getEmptyBlockHeaderMock(); |
||||||
|
final BlockHeader nonEmptyBlockHeaderMock1 = getNonEmptyBlockHeaderMock("0x01", "0x02"); |
||||||
|
final BlockHeader nonEmptyBlockHeaderMock3 = getNonEmptyBlockHeaderMock("0x03", "0x04"); |
||||||
|
|
||||||
|
final ProtocolSchedule protocolSchedule = getProtocolScheduleMock(); |
||||||
|
final PeerTaskExecutor peerTaskExecutor = mock(PeerTaskExecutor.class); |
||||||
|
when(peerTaskExecutor.execute(any())) |
||||||
|
.thenReturn( |
||||||
|
new PeerTaskExecutorResult<>( |
||||||
|
Optional.of(List.of(block1, block3)), PeerTaskExecutorResponseCode.SUCCESS)); |
||||||
|
|
||||||
|
CompleteBlocksWithPeerTask completeBlocksWithPeerTask = |
||||||
|
new CompleteBlocksWithPeerTask( |
||||||
|
protocolSchedule, |
||||||
|
List.of( |
||||||
|
nonEmptyBlockHeaderMock1, |
||||||
|
emptyBlockHeaderMock, |
||||||
|
nonEmptyBlockHeaderMock3, |
||||||
|
emptyBlockHeaderMock), |
||||||
|
peerTaskExecutor); |
||||||
|
|
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks()).isNotEmpty(); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().size()).isEqualTo(4); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().get(0)).isEqualTo(block1); |
||||||
|
assertThat(BlockHeader.hasEmptyBlock(completeBlocksWithPeerTask.getBlocks().get(1).getHeader())) |
||||||
|
.isTrue(); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().get(2)).isEqualTo(block3); |
||||||
|
assertThat(BlockHeader.hasEmptyBlock(completeBlocksWithPeerTask.getBlocks().get(3).getHeader())) |
||||||
|
.isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldRequestMoreBodiesUntilFinished() { |
||||||
|
final Block block1 = mock(Block.class); |
||||||
|
final Block block3 = mock(Block.class); |
||||||
|
final BlockHeader emptyBlockHeaderMock = getEmptyBlockHeaderMock(); |
||||||
|
final BlockHeader nonEmptyBlockHeaderMock1 = getNonEmptyBlockHeaderMock("0x01", "0x02"); |
||||||
|
final BlockHeader nonEmptyBlockHeaderMock3 = getNonEmptyBlockHeaderMock("0x03", "0x04"); |
||||||
|
|
||||||
|
final ProtocolSchedule protocolSchedule = getProtocolScheduleMock(); |
||||||
|
final PeerTaskExecutor peerTaskExecutor = mock(PeerTaskExecutor.class); |
||||||
|
when(peerTaskExecutor.execute(any())) |
||||||
|
.thenReturn( |
||||||
|
new PeerTaskExecutorResult<>( |
||||||
|
Optional.of(List.of(block1)), PeerTaskExecutorResponseCode.SUCCESS)) |
||||||
|
.thenReturn( |
||||||
|
new PeerTaskExecutorResult<>( |
||||||
|
Optional.of(List.of(block3)), PeerTaskExecutorResponseCode.SUCCESS)); |
||||||
|
|
||||||
|
CompleteBlocksWithPeerTask completeBlocksWithPeerTask = |
||||||
|
new CompleteBlocksWithPeerTask( |
||||||
|
protocolSchedule, |
||||||
|
List.of( |
||||||
|
nonEmptyBlockHeaderMock1, |
||||||
|
emptyBlockHeaderMock, |
||||||
|
nonEmptyBlockHeaderMock3, |
||||||
|
emptyBlockHeaderMock), |
||||||
|
peerTaskExecutor); |
||||||
|
|
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks()).isNotEmpty(); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().size()).isEqualTo(4); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().get(0)).isEqualTo(block1); |
||||||
|
assertThat(BlockHeader.hasEmptyBlock(completeBlocksWithPeerTask.getBlocks().get(1).getHeader())) |
||||||
|
.isTrue(); |
||||||
|
assertThat(completeBlocksWithPeerTask.getBlocks().get(2)).isEqualTo(block3); |
||||||
|
assertThat(BlockHeader.hasEmptyBlock(completeBlocksWithPeerTask.getBlocks().get(3).getHeader())) |
||||||
|
.isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
private static ProtocolSchedule getProtocolScheduleMock() { |
||||||
|
final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); |
||||||
|
final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); |
||||||
|
final Optional<WithdrawalsProcessor> optional = Optional.of(mock(WithdrawalsProcessor.class)); |
||||||
|
when(protocolSpec.getWithdrawalsProcessor()).thenReturn(optional); |
||||||
|
when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); |
||||||
|
return protocolSchedule; |
||||||
|
} |
||||||
|
|
||||||
|
private static BlockHeader getEmptyBlockHeaderMock() { |
||||||
|
final BlockHeader blockHeader = mock(BlockHeader.class); |
||||||
|
when(blockHeader.getTransactionsRoot()).thenReturn(Hash.EMPTY_TRIE_HASH); |
||||||
|
when(blockHeader.getOmmersHash()).thenReturn(Hash.EMPTY_LIST_HASH); |
||||||
|
when(blockHeader.getWithdrawalsRoot()).thenReturn(Optional.empty()); |
||||||
|
when(blockHeader.getRequestsRoot()).thenReturn(Optional.empty()); |
||||||
|
return blockHeader; |
||||||
|
} |
||||||
|
|
||||||
|
private static BlockHeader getNonEmptyBlockHeaderMock( |
||||||
|
final String transactionsRootHexString, final String ommersHash) { |
||||||
|
final BlockHeader blockHeader = mock(BlockHeader.class); |
||||||
|
when(blockHeader.getTransactionsRoot()) |
||||||
|
.thenReturn(Hash.fromHexStringLenient(transactionsRootHexString)); |
||||||
|
when(blockHeader.getOmmersHash()).thenReturn(Hash.fromHexStringLenient(ommersHash)); |
||||||
|
when(blockHeader.getWithdrawalsRoot()).thenReturn(Optional.empty()); |
||||||
|
when(blockHeader.getRequestsRoot()).thenReturn(Optional.empty()); |
||||||
|
return blockHeader; |
||||||
|
} |
||||||
|
|
||||||
|
private static List<Block> getExpectedBlocks( |
||||||
|
final BlockHeader header1, final BlockHeader header2) { |
||||||
|
final Block block1 = |
||||||
|
new Block( |
||||||
|
header1, |
||||||
|
new BlockBody( |
||||||
|
Collections.emptyList(), |
||||||
|
Collections.emptyList(), |
||||||
|
Optional.empty(), |
||||||
|
Optional.empty())); |
||||||
|
final Block block2 = |
||||||
|
new Block( |
||||||
|
header2, |
||||||
|
new BlockBody( |
||||||
|
Collections.emptyList(), |
||||||
|
Collections.emptyList(), |
||||||
|
Optional.of(Collections.emptyList()), |
||||||
|
Optional.empty())); |
||||||
|
|
||||||
|
return asList(block1, block2); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue