Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix empty body concept after shanghai #5174

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.hyperledger.besu.plugin.services.MetricsSystem;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -74,12 +75,32 @@ private CompleteBlocksTask(
this.blocks =
headers.stream()
.filter(this::hasEmptyBody)
.collect(toMap(BlockHeader::getNumber, header -> new Block(header, BlockBody.empty())));
.collect(
toMap(
BlockHeader::getNumber,
header ->
new Block(
header,
new BlockBody(
Collections.emptyList(),
Collections.emptyList(),
isWithdrawalsEnabled(protocolSchedule, header)
? Optional.of(Collections.emptyList())
: Optional.empty()))));
gfukushima marked this conversation as resolved.
Show resolved Hide resolved
}

private static boolean isWithdrawalsEnabled(
final ProtocolSchedule protocolSchedule, final BlockHeader header) {
return protocolSchedule.getByBlockHeader(header).getWithdrawalsProcessor().isPresent();
}

gfukushima marked this conversation as resolved.
Show resolved Hide resolved
private boolean hasEmptyBody(final BlockHeader header) {
return header.getOmmersHash().equals(Hash.EMPTY_LIST_HASH)
&& header.getTransactionsRoot().equals(Hash.EMPTY_TRIE_HASH);
&& header.getTransactionsRoot().equals(Hash.EMPTY_TRIE_HASH)
&& header
.getWithdrawalsRoot()
jframe marked this conversation as resolved.
Show resolved Hide resolved
.map(wsRoot -> wsRoot.equals(Hash.EMPTY_TRIE_HASH))
.orElse(true);
}

public static CompleteBlocksTask forHeaders(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,42 @@
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.GWei;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.Blockchain;
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.core.Withdrawal;
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil;
import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer;
import org.hyperledger.besu.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest;
import org.hyperledger.besu.ethereum.eth.manager.exceptions.MaxRetriesReachedException;
import org.hyperledger.besu.ethereum.eth.manager.task.EthTask;
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.mainnet.WithdrawalsProcessor;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import org.apache.tuweni.units.bigints.UInt64;
import org.junit.Test;
import org.mockito.Mockito;

public class CompleteBlocksTaskTest extends RetryingMessageTaskTest<List<Block>> {

Expand Down Expand Up @@ -79,6 +95,115 @@ public void shouldCompleteWithoutPeersWhenAllBlocksAreEmpty() {
assertThat(task.run()).isCompletedWithValue(blocks);
}

@Test
public void shouldCreateCorrectEmptyBlockWhenWithdrawalsAreEnabled() {
jframe marked this conversation as resolved.
Show resolved Hide resolved
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 Block block1 =
new Block(header1, new BlockBody(Collections.emptyList(), Collections.emptyList()));
final Block block2 =
new Block(
header2,
new BlockBody(
Collections.emptyList(),
Collections.emptyList(),
Optional.of(Collections.emptyList())));

final List<Block> expectedBlocks = asList(block1, block2);
final EthTask<List<Block>> task =
CompleteBlocksTask.forHeaders(
mockProtocolSchedule,
ethContext,
List.of(header1, header2),
maxRetries,
new NoOpMetricsSystem());
assertThat(task.run()).isCompletedWithValue(expectedBlocks);
}

@Test
public void shouldCompleteWhenBlocksAreEmptyExceptForWithdrawals() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this testing that when blocks are empty but have withdrawals they are still downloaded? i.e. that they are considered empty blocks. Was a bit confused from the test name

Copy link
Contributor

@siladu siladu Mar 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this testing that when blocks are empty but have withdrawals they are still downloaded?

yes, i.e. that they are not considered empty blocks like they were being before.

Any name suggestion? This was an modification of the existing test's name: shouldCompleteWithoutPeersWhenAllBlocksAreEmpty. Agree the new one could be better

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 Withdrawal withdrawal =
new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE);
final List<Withdrawal> withdrawals = List.of(withdrawal);
final Hash withdrawalsRoot = BodyValidation.withdrawalsRoot(withdrawals);

final BlockHeader preWithdrawalsHeader = new BlockHeaderTestFixture().number(1).buildHeader();
final BlockHeader firstWithdrawalsHeader =
new BlockHeaderTestFixture().number(2).withdrawalsRoot(withdrawalsRoot).buildHeader();
final BlockHeader secondWithdrawalsHeader =
new BlockHeaderTestFixture().number(3).withdrawalsRoot(Hash.EMPTY_TRIE_HASH).buildHeader();

final Block block1 = new Block(preWithdrawalsHeader, BlockBody.empty());
gfukushima marked this conversation as resolved.
Show resolved Hide resolved
final Block block2 =
new Block(
firstWithdrawalsHeader,
new BlockBody(
Collections.emptyList(), Collections.emptyList(), Optional.of(withdrawals)));
final Block block3 =
gfukushima marked this conversation as resolved.
Show resolved Hide resolved
new Block(
secondWithdrawalsHeader,
new BlockBody(
Collections.emptyList(),
Collections.emptyList(),
Optional.of(Collections.emptyList())));
final List<Block> expected = asList(block1, block2, block3);

final RespondingEthPeer respondingPeer =
EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000);
final RespondingEthPeer.Responder responder = responderForFakeBlocks(expected);
gfukushima marked this conversation as resolved.
Show resolved Hide resolved

when(mockProtocolSchedule.getByBlockHeader((eq(preWithdrawalsHeader))))
.thenReturn(mockParisSpec);
when(mockParisSpec.getWithdrawalsProcessor()).thenReturn(Optional.empty());
when(mockProtocolSchedule.getByBlockHeader((eq(secondWithdrawalsHeader))))
.thenReturn(mockShanghaiSpec);
when(mockShanghaiSpec.getWithdrawalsProcessor())
.thenReturn(Optional.of(mockWithdrawalsProcessor));

final EthTask<List<Block>> task =
CompleteBlocksTask.forHeaders(
mockProtocolSchedule,
ethContext,
List.of(preWithdrawalsHeader, firstWithdrawalsHeader, secondWithdrawalsHeader),
maxRetries,
new NoOpMetricsSystem());

final CompletableFuture<List<Block>> runningTask = task.run();

assertThat(runningTask).isNotDone();
respondingPeer.respond(responder);
assertThat(runningTask).isCompletedWithValue(expected);
// assertThat(task.run()).isCompletedWithValue(expected);
gfukushima marked this conversation as resolved.
Show resolved Hide resolved
}

private RespondingEthPeer.Responder responderForFakeBlocks(final List<Block> blocks) {
final Blockchain mockBlockchain = spy(blockchain);
for (Block block : blocks) {
when(mockBlockchain.getBlockBody(block.getHash())).thenReturn(Optional.of(block.getBody()));
}

return RespondingEthPeer.blockchainResponder(mockBlockchain);
}

@SuppressWarnings("unchecked")
@Test
public void shouldReduceTheBlockSegmentSizeAfterEachRetry() {
Expand Down