From 91c7f37e92b2f8debed964829d99eb0132458f63 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 19 Mar 2024 18:06:15 +0000 Subject: [PATCH 1/4] Ensure empty withdrawal lists are set in BFT blocks when the protocol schedule is shanghai or higher Signed-off-by: Matthew Whitehead --- CHANGELOG.md | 1 + .../bft/BaseBftProtocolScheduleBuilder.java | 4 --- .../bft/blockcreation/BftBlockCreator.java | 7 ++++ .../ibft/statemachine/IbftRound.java | 33 +++++++++++++++++-- .../blockcreation/PkiQbftBlockCreator.java | 7 ++++ .../qbft/statemachine/QbftRound.java | 31 +++++++++++++++-- .../qbft/messagewrappers/ProposalTest.java | 6 +++- .../blockcreation/AbstractBlockCreator.java | 20 +++++++++++ .../ethereum/blockcreation/BlockCreator.java | 2 ++ 9 files changed, 102 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e07c26d4a..170a8866202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Fix txpool dump/restore race condition [#6665](https://github.com/hyperledger/besu/pull/6665) - Make block transaction selection max time aware of PoA transitions [#6676](https://github.com/hyperledger/besu/pull/6676) - Don't enable the BFT mining coordinator when running sub commands such as `blocks export` [#6675](https://github.com/hyperledger/besu/pull/6675) +- Fix Shanghai/QBFT block import bug when syncing new nodes [#6765](https://github.com/hyperledger/besu/pull/6765) ### Download Links diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilder.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilder.java index 2578a805686..166a2d84a36 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilder.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilder.java @@ -30,7 +30,6 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; -import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -125,9 +124,6 @@ private ProtocolSpecBuilder applyBftChanges( .skipZeroBlockRewards(true) .blockHeaderFunctions(BftBlockHeaderFunctions.forOnchainBlock(bftExtraDataCodec)) .blockReward(Wei.of(configOptions.getBlockRewardWei())) - .withdrawalsValidator( - new WithdrawalsValidator - .ProhibitedWithdrawals()) // QBFT/IBFT doesn't support withdrawals .miningBeneficiaryCalculator( header -> configOptions.getMiningBeneficiary().orElseGet(header::getCoinbase)); } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java index e6a981cec78..7097641db37 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import java.util.Collections; import java.util.Optional; /** The Bft block creator. */ @@ -83,6 +84,12 @@ private static MiningBeneficiaryCalculator miningBeneficiaryCalculator( forksSchedule.getFork(blockNum).getValue().getMiningBeneficiary().orElse(localAddress); } + @Override + public BlockCreationResult createEmptyWithdrawalsBlock(final long timestamp) { + return createBlock( + Optional.empty(), Optional.empty(), Optional.of(Collections.emptyList()), timestamp); + } + @Override protected BlockHeader createFinalBlockHeader(final SealableBlockHeader sealableBlockHeader) { final BlockHeaderBuilder builder = diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java index ec4405e0485..f90a595b157 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftHelpers; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.ibft.messagewrappers.Commit; @@ -41,6 +42,8 @@ import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -118,7 +121,20 @@ public ConsensusRoundIdentifier getRoundIdentifier() { * @param headerTimeStampSeconds the header time stamp seconds */ public void createAndSendProposalMessage(final long headerTimeStampSeconds) { - final Block block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); + + // Determine if we're at a protocol spec that requires withdrawals (even if empty) to be present + ProtocolSpec protocolSpec = + ((BftProtocolSchedule) protocolSchedule) + .getByBlockNumberOrTimestamp( + roundState.getRoundIdentifier().getSequenceNumber(), headerTimeStampSeconds); + + final Block block; + if (protocolSpec.getWithdrawalsValidator() instanceof WithdrawalsValidator.AllowedWithdrawals) { + block = blockCreator.createEmptyWithdrawalsBlock(headerTimeStampSeconds).getBlock(); + } else { + block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); + } + final BftExtraData extraData = bftExtraDataCodec.decode(block.getHeader()); LOG.debug("Creating proposed block. round={}", roundState.getRoundIdentifier()); LOG.trace( @@ -141,7 +157,20 @@ public void startRoundWith( final Block blockToPublish; if (!bestBlockFromRoundChange.isPresent()) { LOG.debug("Sending proposal with new block. round={}", roundState.getRoundIdentifier()); - blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); + + // Determine if we're at a protocol spec that requires withdrawals (even if empty) to be + // present + ProtocolSpec protocolSpec = + ((BftProtocolSchedule) protocolSchedule) + .getByBlockNumberOrTimestamp( + roundState.getRoundIdentifier().getSequenceNumber(), headerTimestamp); + + if (protocolSpec.getWithdrawalsValidator() + instanceof WithdrawalsValidator.AllowedWithdrawals) { + blockToPublish = blockCreator.createEmptyWithdrawalsBlock(headerTimestamp).getBlock(); + } else { + blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); + } } else { LOG.debug( "Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier()); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java index 4ec6ea9f8aa..f2e5542db77 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java @@ -114,6 +114,13 @@ public BlockCreationResult createBlock( timestamp); } + @Override + public BlockCreationResult createEmptyWithdrawalsBlock(final long timestamp) { + final BlockCreationResult blockCreationResult = + blockCreator.createEmptyWithdrawalsBlock(timestamp); + return replaceCmsInBlock(blockCreationResult); + } + private BlockCreationResult replaceCmsInBlock(final BlockCreationResult blockCreationResult) { final Block block = blockCreationResult.getBlock(); final BlockHeader blockHeader = block.getHeader(); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java index 5754592064b..bf3ae47068d 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftHelpers; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.payload.SignedData; @@ -45,6 +46,8 @@ import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -129,8 +132,19 @@ public ConsensusRoundIdentifier getRoundIdentifier() { */ public void createAndSendProposalMessage(final long headerTimeStampSeconds) { LOG.debug("Creating proposed block. round={}", roundState.getRoundIdentifier()); - final Block block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); + // Determine if we're at a protocol spec that requires withdrawals (even if empty) to be present + ProtocolSpec protocolSpec = + ((BftProtocolSchedule) protocolSchedule) + .getByBlockNumberOrTimestamp( + roundState.getRoundIdentifier().getSequenceNumber(), headerTimeStampSeconds); + + final Block block; + if (protocolSpec.getWithdrawalsValidator() instanceof WithdrawalsValidator.AllowedWithdrawals) { + block = blockCreator.createEmptyWithdrawalsBlock(headerTimeStampSeconds).getBlock(); + } else { + block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); + } LOG.trace("Creating proposed block blockHeader={}", block.getHeader()); updateStateWithProposalAndTransmit(block, emptyList(), emptyList()); } @@ -148,8 +162,21 @@ public void startRoundWith( final Block blockToPublish; if (bestPreparedCertificate.isEmpty()) { + + // Determine if we're at a protocol spec that requires withdrawals (even if empty) to be + // present + ProtocolSpec protocolSpec = + ((BftProtocolSchedule) protocolSchedule) + .getByBlockNumberOrTimestamp( + roundState.getRoundIdentifier().getSequenceNumber(), headerTimestamp); + + if (protocolSpec.getWithdrawalsValidator() + instanceof WithdrawalsValidator.AllowedWithdrawals) { + blockToPublish = blockCreator.createEmptyWithdrawalsBlock(headerTimestamp).getBlock(); + } else { + blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); + } LOG.debug("Sending proposal with new block. round={}", roundState.getRoundIdentifier()); - blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); } else { LOG.debug( "Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier()); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java index 46a079ef00a..b3c462656ac 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java @@ -51,7 +51,11 @@ public class ProposalTest { private static final Block BLOCK = new Block( new BlockHeaderTestFixture().extraData(bftExtraDataCodec.encode(extraData)).buildHeader(), - new BlockBody(Collections.emptyList(), Collections.emptyList())); + new BlockBody( + Collections.emptyList(), + Collections.emptyList(), + Optional.of(Collections.emptyList()), + Optional.empty())); @Test public void canRoundTripProposalMessage() { diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index f7503c65791..451b337b178 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -159,6 +159,26 @@ public BlockCreationResult createBlock( true); } + @Override + public BlockCreationResult createEmptyWithdrawalsBlock(final long timestamp) { + throw new UnsupportedOperationException("Only used by BFT block creators"); + } + + public BlockCreationResult createBlock( + final Optional> maybeTransactions, + final Optional> maybeOmmers, + final Optional> maybeWithdrawals, + final long timestamp) { + return createBlock( + maybeTransactions, + maybeOmmers, + maybeWithdrawals, + Optional.empty(), + Optional.empty(), + timestamp, + true); + } + protected BlockCreationResult createBlock( final Optional> maybeTransactions, final Optional> maybeOmmers, diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java index 5931d49f369..389c0c16f81 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java @@ -44,6 +44,8 @@ public TransactionSelectionResults getTransactionSelectionResults() { BlockCreationResult createBlock(final long timestamp); + BlockCreationResult createEmptyWithdrawalsBlock(final long timestamp); + BlockCreationResult createBlock( final List transactions, final List ommers, final long timestamp); From 9454dd03bb514699265d2030d55733d10695d23c Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 19 Mar 2024 19:26:37 +0000 Subject: [PATCH 2/4] Unit tests missing mock Signed-off-by: Matthew Whitehead --- .../besu/consensus/qbft/statemachine/QbftRoundTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java index bf730bbf7e6..2bb018f0c0d 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java @@ -147,6 +147,9 @@ QbftContext.class, emptyList(), new QbftExtraDataCodec()), when(blockImporter.importBlock(any(), any(), any())) .thenReturn(new BlockImportResult(BlockImportResult.BlockImportStatus.IMPORTED)); + when(protocolSchedule.getByBlockNumberOrTimestamp(anyLong(), anyLong())) + .thenReturn(protocolSpec); + subscribers.subscribe(minedBlockObserver); } From 5db31e2dfefdd7c1d86370a50cc8466f8f7b5e3b Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 19 Mar 2024 19:48:23 +0000 Subject: [PATCH 3/4] Unit tests missing mock Signed-off-by: Matthew Whitehead --- .../besu/consensus/ibft/statemachine/IbftRoundTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java index 5057b057409..e6126ce910e 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java @@ -139,6 +139,10 @@ public void setup() { .when(blockImporter.importBlock(any(), any(), any())) .thenReturn(new BlockImportResult(BlockImportResult.BlockImportStatus.IMPORTED)); + lenient() + .when(protocolSchedule.getByBlockNumberOrTimestamp(anyLong(), anyLong())) + .thenReturn(protocolSpec); + subscribers.subscribe(minedBlockObserver); } From 082ba987fab5756c9b9333b84e55dd3b0f0d4af6 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Thu, 21 Mar 2024 11:02:43 +0000 Subject: [PATCH 4/4] Refactor and reduce code duplication Signed-off-by: Matthew Whitehead --- .../bft/blockcreation/BftBlockCreator.java | 16 +++ .../ibft/statemachine/IbftRound.java | 33 +---- .../blockcreation/BftBlockCreatorTest.java | 121 ++++++++++++++++++ .../ibft/statemachine/IbftRoundTest.java | 8 +- .../blockcreation/PkiQbftBlockCreator.java | 37 +++++- .../QbftBlockCreatorFactory.java | 6 +- .../qbft/statemachine/QbftRound.java | 31 +---- .../PkiQbftBlockCreatorTest.java | 31 ++++- .../qbft/statemachine/QbftRoundTest.java | 3 - 9 files changed, 212 insertions(+), 74 deletions(-) diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java index 7097641db37..b84cd665f14 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftHelpers; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.AbstractBlockCreator; @@ -29,6 +30,8 @@ import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import java.util.Collections; import java.util.Optional; @@ -78,6 +81,19 @@ public BftBlockCreator( this.bftExtraDataCodec = bftExtraDataCodec; } + @Override + public BlockCreationResult createBlock(final long timestamp) { + ProtocolSpec protocolSpec = + ((BftProtocolSchedule) protocolSchedule) + .getByBlockNumberOrTimestamp(parentHeader.getNumber() + 1, timestamp); + + if (protocolSpec.getWithdrawalsValidator() instanceof WithdrawalsValidator.AllowedWithdrawals) { + return createEmptyWithdrawalsBlock(timestamp); + } else { + return createBlock(Optional.empty(), Optional.empty(), timestamp); + } + } + private static MiningBeneficiaryCalculator miningBeneficiaryCalculator( final Address localAddress, final ForksSchedule forksSchedule) { return blockNum -> diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java index f90a595b157..ec4405e0485 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java @@ -21,7 +21,6 @@ import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftHelpers; -import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.ibft.messagewrappers.Commit; @@ -42,8 +41,6 @@ import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -121,20 +118,7 @@ public ConsensusRoundIdentifier getRoundIdentifier() { * @param headerTimeStampSeconds the header time stamp seconds */ public void createAndSendProposalMessage(final long headerTimeStampSeconds) { - - // Determine if we're at a protocol spec that requires withdrawals (even if empty) to be present - ProtocolSpec protocolSpec = - ((BftProtocolSchedule) protocolSchedule) - .getByBlockNumberOrTimestamp( - roundState.getRoundIdentifier().getSequenceNumber(), headerTimeStampSeconds); - - final Block block; - if (protocolSpec.getWithdrawalsValidator() instanceof WithdrawalsValidator.AllowedWithdrawals) { - block = blockCreator.createEmptyWithdrawalsBlock(headerTimeStampSeconds).getBlock(); - } else { - block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); - } - + final Block block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); final BftExtraData extraData = bftExtraDataCodec.decode(block.getHeader()); LOG.debug("Creating proposed block. round={}", roundState.getRoundIdentifier()); LOG.trace( @@ -157,20 +141,7 @@ public void startRoundWith( final Block blockToPublish; if (!bestBlockFromRoundChange.isPresent()) { LOG.debug("Sending proposal with new block. round={}", roundState.getRoundIdentifier()); - - // Determine if we're at a protocol spec that requires withdrawals (even if empty) to be - // present - ProtocolSpec protocolSpec = - ((BftProtocolSchedule) protocolSchedule) - .getByBlockNumberOrTimestamp( - roundState.getRoundIdentifier().getSequenceNumber(), headerTimestamp); - - if (protocolSpec.getWithdrawalsValidator() - instanceof WithdrawalsValidator.AllowedWithdrawals) { - blockToPublish = blockCreator.createEmptyWithdrawalsBlock(headerTimestamp).getBlock(); - } else { - blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); - } + blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); } else { LOG.debug( "Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier()); diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java index 25c5c1d10b8..44e082af7f1 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java @@ -213,4 +213,125 @@ public BlockHeaderValidator.Builder createBlockHeaderRuleset( new BftBlockHashing(bftExtraDataEncoder) .calculateDataHashForCommittedSeal(header, extraData)); } + + @Test + public void testBlockCreationResultsInEmptyWithdrawalsListShanghaiOnwards() { + // Construct a parent block. + final BlockHeaderTestFixture blockHeaderBuilder = new BlockHeaderTestFixture(); + blockHeaderBuilder.gasLimit(5000); // required to pass validation rule checks. + final BlockHeader parentHeader = blockHeaderBuilder.buildHeader(); + final Optional optionalHeader = Optional.of(parentHeader); + + // Construct a blockchain and world state + final MutableBlockchain blockchain = mock(MutableBlockchain.class); + when(blockchain.getChainHeadHash()).thenReturn(parentHeader.getHash()); + when(blockchain.getBlockHeader(any())).thenReturn(optionalHeader); + final BlockHeader blockHeader = mock(BlockHeader.class); + when(blockHeader.getBaseFee()).thenReturn(Optional.empty()); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); + + final List
initialValidatorList = Lists.newArrayList(); + for (int i = 0; i < 4; i++) { + initialValidatorList.add(AddressHelpers.ofValue(i)); + } + + final IbftExtraDataCodec bftExtraDataEncoder = new IbftExtraDataCodec(); + + final BaseBftProtocolScheduleBuilder bftProtocolSchedule = + new BaseBftProtocolScheduleBuilder() { + @Override + public BlockHeaderValidator.Builder createBlockHeaderRuleset( + final BftConfigOptions config, final FeeMarket feeMarket) { + return IbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( + 5, Optional.empty()); + } + }; + final GenesisConfigOptions configOptions = + GenesisConfigFile.fromConfig("{\"config\": {\"shanghaiTime\":0}}").getConfigOptions(); + final ForksSchedule forksSchedule = + new ForksSchedule<>(List.of(new ForkSpec<>(0, configOptions.getBftConfigOptions()))); + final ProtocolSchedule protocolSchedule = + bftProtocolSchedule.createProtocolSchedule( + configOptions, + forksSchedule, + PrivacyParameters.DEFAULT, + false, + bftExtraDataEncoder, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager()); + final ProtocolContext protContext = + new ProtocolContext( + blockchain, + createInMemoryWorldStateArchive(), + setupContextWithBftExtraDataEncoder(initialValidatorList, bftExtraDataEncoder), + new BadBlockManager()); + + final TransactionPoolConfiguration poolConf = + ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(); + + final GasPricePendingTransactionsSorter pendingTransactions = + new GasPricePendingTransactionsSorter( + poolConf, + TestClock.system(ZoneId.systemDefault()), + metricsSystem, + blockchain::getChainHeadHeader); + + final EthContext ethContext = mock(EthContext.class, RETURNS_DEEP_STUBS); + when(ethContext.getEthPeers().subscribeConnect(any())).thenReturn(1L); + + final TransactionPool transactionPool = + new TransactionPool( + () -> pendingTransactions, + protocolSchedule, + protContext, + mock(TransactionBroadcaster.class), + ethContext, + new TransactionPoolMetrics(metricsSystem), + poolConf); + + transactionPool.setEnabled(); + + final MiningParameters miningParameters = + ImmutableMiningParameters.builder() + .mutableInitValues( + MutableInitValues.builder() + .extraData( + bftExtraDataEncoder.encode( + new BftExtraData( + Bytes.wrap(new byte[32]), + Collections.emptyList(), + Optional.empty(), + 0, + initialValidatorList))) + .minTransactionGasPrice(Wei.ZERO) + .coinbase(AddressHelpers.ofValue(1)) + .build()) + .build(); + + final BftBlockCreator blockCreator = + new BftBlockCreator( + miningParameters, + forksSchedule, + initialValidatorList.get(0), + parent -> + bftExtraDataEncoder.encode( + new BftExtraData( + Bytes.wrap(new byte[32]), + Collections.emptyList(), + Optional.empty(), + 0, + initialValidatorList)), + transactionPool, + protContext, + protocolSchedule, + parentHeader, + bftExtraDataEncoder, + new DeterministicEthScheduler()); + + final Block block = blockCreator.createBlock(parentHeader.getTimestamp() + 1).getBlock(); + + // Test that a BFT block contains an empty withdrawals list (not an optional empty) + assertThat(block.getBody().getWithdrawals().isPresent()).isTrue(); + } } diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java index e6126ce910e..e93f2f9f40c 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java @@ -59,6 +59,7 @@ import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -139,10 +140,6 @@ public void setup() { .when(blockImporter.importBlock(any(), any(), any())) .thenReturn(new BlockImportResult(BlockImportResult.BlockImportStatus.IMPORTED)); - lenient() - .when(protocolSchedule.getByBlockNumberOrTimestamp(anyLong(), anyLong())) - .thenReturn(protocolSpec); - subscribers.subscribe(minedBlockObserver); } @@ -277,6 +274,9 @@ public void twoValidatorNetworkSendsPrepareOnProposalReceptionThenSendsCommitOnC @Test public void localNodeProposesToNetworkOfTwoValidatorsImportsOnReceptionOfCommitFromPeer() { + lenient() + .when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.AllowedWithdrawals()); final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); final IbftRound round = new IbftRound( diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java index f2e5542db77..ea4c851b7f2 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; import org.hyperledger.besu.consensus.qbft.pki.PkiQbftBlockHeaderFunctions; import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraData; @@ -29,6 +30,9 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.pki.cms.CmsCreator; import java.util.Collections; @@ -48,6 +52,8 @@ public class PkiQbftBlockCreator implements BlockCreator { private final BlockCreator blockCreator; private final PkiQbftExtraDataCodec pkiQbftExtraDataCodec; private final CmsCreator cmsCreator; + private final BlockHeader parentHeader; + private final ProtocolSchedule protocolSchedule; /** * Instantiates a new Pki qbft block creator. @@ -55,17 +61,24 @@ public class PkiQbftBlockCreator implements BlockCreator { * @param blockCreator the block creator * @param pkiBlockCreationConfiguration the pki block creation configuration * @param pkiQbftExtraDataCodec the pki qbft extra data codec + * @param parentHeader the block header of the parent block + * @param protocolSchedule the protocol schedule (the type of block can vary based on the current + * protocol spec) */ public PkiQbftBlockCreator( final BlockCreator blockCreator, final PkiBlockCreationConfiguration pkiBlockCreationConfiguration, - final BftExtraDataCodec pkiQbftExtraDataCodec) { + final BftExtraDataCodec pkiQbftExtraDataCodec, + final BlockHeader parentHeader, + final ProtocolSchedule protocolSchedule) { this( blockCreator, new CmsCreator( pkiBlockCreationConfiguration.getKeyStore(), pkiBlockCreationConfiguration.getCertificateAlias()), - pkiQbftExtraDataCodec); + pkiQbftExtraDataCodec, + parentHeader, + protocolSchedule); } /** @@ -74,14 +87,21 @@ public PkiQbftBlockCreator( * @param blockCreator the block creator * @param cmsCreator the cms creator * @param bftExtraDataCodec the bft extra data codec + * @param parentHeader the block header of the parent block + * @param protocolSchedule the protocol schedule (the type of block can vary based on the current + * protocol spec) */ @VisibleForTesting public PkiQbftBlockCreator( final BlockCreator blockCreator, final CmsCreator cmsCreator, - final BftExtraDataCodec bftExtraDataCodec) { + final BftExtraDataCodec bftExtraDataCodec, + final BlockHeader parentHeader, + final ProtocolSchedule protocolSchedule) { this.blockCreator = blockCreator; this.cmsCreator = cmsCreator; + this.protocolSchedule = protocolSchedule; + this.parentHeader = parentHeader; checkArgument( bftExtraDataCodec instanceof PkiQbftExtraDataCodec, @@ -91,7 +111,16 @@ public PkiQbftBlockCreator( @Override public BlockCreationResult createBlock(final long timestamp) { - final BlockCreationResult blockCreationResult = blockCreator.createBlock(timestamp); + ProtocolSpec protocolSpec = + ((BftProtocolSchedule) protocolSchedule) + .getByBlockNumberOrTimestamp(parentHeader.getNumber() + 1, timestamp); + + final BlockCreationResult blockCreationResult; + if (protocolSpec.getWithdrawalsValidator() instanceof WithdrawalsValidator.AllowedWithdrawals) { + blockCreationResult = blockCreator.createEmptyWithdrawalsBlock(timestamp); + } else { + blockCreationResult = blockCreator.createBlock(timestamp); + } return replaceCmsInBlock(blockCreationResult); } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java index 5bb87363b39..9cbc64e787e 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java @@ -77,7 +77,11 @@ public BlockCreator create(final BlockHeader parentHeader, final int round) { return blockCreator; } else { return new PkiQbftBlockCreator( - blockCreator, qbftContext.getPkiBlockCreationConfiguration().get(), bftExtraDataCodec); + blockCreator, + qbftContext.getPkiBlockCreationConfiguration().get(), + bftExtraDataCodec, + parentHeader, + protocolSchedule); } } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java index bf3ae47068d..5754592064b 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftHelpers; -import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.payload.SignedData; @@ -46,8 +45,6 @@ import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -132,19 +129,8 @@ public ConsensusRoundIdentifier getRoundIdentifier() { */ public void createAndSendProposalMessage(final long headerTimeStampSeconds) { LOG.debug("Creating proposed block. round={}", roundState.getRoundIdentifier()); + final Block block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); - // Determine if we're at a protocol spec that requires withdrawals (even if empty) to be present - ProtocolSpec protocolSpec = - ((BftProtocolSchedule) protocolSchedule) - .getByBlockNumberOrTimestamp( - roundState.getRoundIdentifier().getSequenceNumber(), headerTimeStampSeconds); - - final Block block; - if (protocolSpec.getWithdrawalsValidator() instanceof WithdrawalsValidator.AllowedWithdrawals) { - block = blockCreator.createEmptyWithdrawalsBlock(headerTimeStampSeconds).getBlock(); - } else { - block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); - } LOG.trace("Creating proposed block blockHeader={}", block.getHeader()); updateStateWithProposalAndTransmit(block, emptyList(), emptyList()); } @@ -162,21 +148,8 @@ public void startRoundWith( final Block blockToPublish; if (bestPreparedCertificate.isEmpty()) { - - // Determine if we're at a protocol spec that requires withdrawals (even if empty) to be - // present - ProtocolSpec protocolSpec = - ((BftProtocolSchedule) protocolSchedule) - .getByBlockNumberOrTimestamp( - roundState.getRoundIdentifier().getSequenceNumber(), headerTimestamp); - - if (protocolSpec.getWithdrawalsValidator() - instanceof WithdrawalsValidator.AllowedWithdrawals) { - blockToPublish = blockCreator.createEmptyWithdrawalsBlock(headerTimestamp).getBlock(); - } else { - blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); - } LOG.debug("Sending proposal with new block. round={}", roundState.getRoundIdentifier()); + blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); } else { LOG.debug( "Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier()); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java index 669563d6f5b..1a597b790e6 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hyperledger.besu.consensus.common.bft.BftExtraDataFixture.createExtraData; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -25,6 +26,7 @@ import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; import org.hyperledger.besu.consensus.common.bft.BftExtraData; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.pki.PkiQbftBlockHeaderFunctions; import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraData; @@ -37,6 +39,8 @@ 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.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.pki.cms.CmsCreator; import java.util.Collections; @@ -53,21 +57,38 @@ public class PkiQbftBlockCreatorTest { private CmsCreator cmsCreator; private PkiQbftBlockCreator pkiQbftBlockCreator; private BlockHeaderTestFixture blockHeaderBuilder; + private BlockHeader blockHeader; + private BftProtocolSchedule protocolSchedule; + private ProtocolSpec protocolSpec; @BeforeEach public void before() { blockCreator = mock(BlockCreator.class); cmsCreator = mock(CmsCreator.class); + blockHeader = mock(BlockHeader.class); + protocolSchedule = mock(BftProtocolSchedule.class); + protocolSpec = mock(ProtocolSpec.class); - pkiQbftBlockCreator = new PkiQbftBlockCreator(blockCreator, cmsCreator, extraDataCodec); + pkiQbftBlockCreator = + new PkiQbftBlockCreator( + blockCreator, cmsCreator, extraDataCodec, blockHeader, protocolSchedule); blockHeaderBuilder = new BlockHeaderTestFixture(); + + when(protocolSchedule.getByBlockNumberOrTimestamp(anyLong(), anyLong())) + .thenReturn(protocolSpec); } @Test public void createProposalBehaviourWithNonPkiCodecFails() { assertThatThrownBy( - () -> new PkiQbftBlockCreator(blockCreator, cmsCreator, new QbftExtraDataCodec())) + () -> + new PkiQbftBlockCreator( + blockCreator, + cmsCreator, + new QbftExtraDataCodec(), + blockHeader, + protocolSchedule)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("PkiQbftBlockCreator must use PkiQbftExtraDataCodec"); } @@ -75,6 +96,8 @@ public void createProposalBehaviourWithNonPkiCodecFails() { @Test public void cmsInProposedBlockHasValueCreatedByCmsCreator() { createBlockBeingProposed(); + when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.AllowedWithdrawals()); final Bytes cms = Bytes.random(32); when(cmsCreator.create(any(Bytes.class))).thenReturn(cms); @@ -89,6 +112,8 @@ public void cmsInProposedBlockHasValueCreatedByCmsCreator() { @Test public void cmsIsCreatedWithCorrectHashingFunction() { + when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals()); final Block block = createBlockBeingProposed(); final Hash expectedHashForCmsCreation = PkiQbftBlockHeaderFunctions.forCmsSignature(extraDataCodec).hash(block.getHeader()); @@ -124,6 +149,8 @@ private Block createBlockBeingProposed() { new BlockBody(Collections.emptyList(), Collections.emptyList())); when(blockCreator.createBlock(eq(1L))) .thenReturn(new BlockCreationResult(block, new TransactionSelectionResults())); + when(blockCreator.createEmptyWithdrawalsBlock(anyLong())) + .thenReturn(new BlockCreationResult(block, new TransactionSelectionResults())); return block; } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java index 2bb018f0c0d..bf730bbf7e6 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java @@ -147,9 +147,6 @@ QbftContext.class, emptyList(), new QbftExtraDataCodec()), when(blockImporter.importBlock(any(), any(), any())) .thenReturn(new BlockImportResult(BlockImportResult.BlockImportStatus.IMPORTED)); - when(protocolSchedule.getByBlockNumberOrTimestamp(anyLong(), anyLong())) - .thenReturn(protocolSpec); - subscribers.subscribe(minedBlockObserver); }