refactors to rely on blocks added, and behave like singleton (#4325)

* refactors to rely on blocks added, and behave like singleton
* pulls up synchronizer building to keep merge related stuff in right place
* shows ready on pre-merge nets. no display on most merge nets
* listens for finalized, explains more pandas

Signed-off-by: Justin Florentine <justin+github@florentine.us>
pull/4362/head
Justin Florentine 2 years ago committed by GitHub
parent 93109ecc2c
commit 101f5efbdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 14
      besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java
  3. 60
      besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java
  4. 109
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PandaPrinter.java
  5. 116
      consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PandaPrinterTest.java

@ -14,6 +14,9 @@
- Filter out disconnected peers when fetching available peers [#4269](https://github.com/hyperledger/besu/pull/4269)
- Updated the default value of fast-sync-min-peers post merge [#4298](https://github.com/hyperledger/besu/pull/4298)
- Log imported block info post merge [#4310](https://github.com/hyperledger/besu/pull/4310)
- Pandas! Pandas now appear in 3 phases: The black bear and polar bear that are preparing? Those will appear when
your client has TTD configured (which is setup by default for mainnet), is in sync, and processing Proof of Work blocks. In the second phase you will see them powering up when the Terminal Total Difficulty block is added to the blockchain.
The final form of the Ethereum Panda will appear when the first finalized block is received from the Consensus Layer.
### Bug Fixes
- Accept wit/80 from Nethermind [#4279](https://github.com/hyperledger/besu/pull/4279)

@ -21,7 +21,6 @@ import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.consensus.merge.FinalizedBlockHashSupplier;
import org.hyperledger.besu.consensus.merge.MergeContext;
import org.hyperledger.besu.consensus.merge.PandaPrinter;
import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.datatypes.Hash;
@ -422,8 +421,6 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
ethProtocolManager,
pivotBlockSelector);
synchronizer.subscribeInSync(new PandaPrinter());
final MiningCoordinator miningCoordinator =
createMiningCoordinator(
protocolSchedule,
@ -467,7 +464,7 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
additionalPluginServices);
}
private Synchronizer createSynchronizer(
protected Synchronizer createSynchronizer(
final ProtocolSchedule protocolSchedule,
final WorldStateStorage worldStateStorage,
final ProtocolContext protocolContext,
@ -477,8 +474,6 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
final EthProtocolManager ethProtocolManager,
final PivotBlockSelector pivotBlockSelector) {
final GenesisConfigOptions maybeForTTD = configOptionsSupplier.get();
DefaultSynchronizer toUse =
new DefaultSynchronizer(
syncConfig,
@ -494,13 +489,6 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
metricsSystem,
getFullSyncTerminationCondition(protocolContext.getBlockchain()),
pivotBlockSelector);
if (maybeForTTD.getTerminalTotalDifficulty().isPresent()) {
LOG.info(
"TTD present, creating DefaultSynchronizer that stops propagating after finalization");
protocolContext
.getConsensusContext(MergeContext.class)
.addNewForkchoiceMessageListener(toUse);
}
return toUse;
}

@ -15,6 +15,8 @@
package org.hyperledger.besu.controller;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.consensus.merge.MergeContext;
import org.hyperledger.besu.consensus.merge.PandaPrinter;
import org.hyperledger.besu.consensus.merge.PostMergeContext;
import org.hyperledger.besu.consensus.merge.TransitionBackwardSyncContext;
@ -29,8 +31,10 @@ import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
import org.hyperledger.besu.ethereum.eth.manager.EthMessages;
@ -39,6 +43,8 @@ import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager;
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
import org.hyperledger.besu.ethereum.eth.manager.MergePeerFilter;
import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator;
import org.hyperledger.besu.ethereum.eth.sync.DefaultSynchronizer;
import org.hyperledger.besu.ethereum.eth.sync.PivotBlockSelector;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext;
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
@ -47,8 +53,10 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfigurati
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.Pruner;
import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;
import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider;
@ -61,10 +69,15 @@ import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TransitionBesuControllerBuilder extends BesuControllerBuilder {
private final BesuControllerBuilder preMergeBesuControllerBuilder;
private final MergeBesuControllerBuilder mergeBesuControllerBuilder;
private static final Logger LOG = LoggerFactory.getLogger(TransitionBesuControllerBuilder.class);
public TransitionBesuControllerBuilder(
final BesuControllerBuilder preMergeBesuControllerBuilder,
final MergeBesuControllerBuilder mergeBesuControllerBuilder) {
@ -176,6 +189,50 @@ public class TransitionBesuControllerBuilder extends BesuControllerBuilder {
return new NoopPluginServiceFactory();
}
@Override
protected Synchronizer createSynchronizer(
final ProtocolSchedule protocolSchedule,
final WorldStateStorage worldStateStorage,
final ProtocolContext protocolContext,
final Optional<Pruner> maybePruner,
final EthContext ethContext,
final SyncState syncState,
final EthProtocolManager ethProtocolManager,
final PivotBlockSelector pivotBlockSelector) {
DefaultSynchronizer sync =
(DefaultSynchronizer)
super.createSynchronizer(
protocolSchedule,
worldStateStorage,
protocolContext,
maybePruner,
ethContext,
syncState,
ethProtocolManager,
pivotBlockSelector);
final GenesisConfigOptions maybeForTTD = configOptionsSupplier.get();
if (maybeForTTD.getTerminalTotalDifficulty().isPresent()) {
LOG.info(
"TTD present, creating DefaultSynchronizer that stops propagating after finalization");
protocolContext.getConsensusContext(MergeContext.class).addNewForkchoiceMessageListener(sync);
Optional<Difficulty> currentTotal =
protocolContext
.getBlockchain()
.getTotalDifficultyByHash(protocolContext.getBlockchain().getChainHeadHash());
PandaPrinter.init(
currentTotal, Difficulty.of(maybeForTTD.getTerminalTotalDifficulty().get()));
sync.subscribeInSync(PandaPrinter.getInstance());
protocolContext.getBlockchain().observeBlockAdded(PandaPrinter.getInstance());
protocolContext
.getConsensusContext(MergeContext.class)
.addNewForkchoiceMessageListener(PandaPrinter.getInstance());
}
return sync;
}
private void initTransitionWatcher(
final ProtocolContext protocolContext, final TransitionCoordinator composedCoordinator) {
@ -193,7 +250,7 @@ public class TransitionBesuControllerBuilder extends BesuControllerBuilder {
if (priorState.filter(prior -> !prior).isPresent()) {
// only print pandas if we had a prior merge state, and it was false
PandaPrinter.printOnFirstCrossing();
PandaPrinter.getInstance().printOnFirstCrossing();
}
} else if (composedCoordinator.isMiningBeforeMerge()) {
@ -219,7 +276,6 @@ public class TransitionBesuControllerBuilder extends BesuControllerBuilder {
@Override
public BesuController build() {
BesuController controller = super.build();
PandaPrinter.hasTTD();
PostMergeContext.get().setSyncState(controller.getSyncState());
return controller;
}

@ -17,6 +17,9 @@
package org.hyperledger.besu.consensus.merge;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
import org.hyperledger.besu.ethereum.chain.BlockAddedObserver;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Synchronizer.InSyncListener;
@ -31,18 +34,49 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PandaPrinter implements InSyncListener, ForkchoiceMessageListener, MergeStateHandler {
public class PandaPrinter implements InSyncListener, ForkchoiceMessageListener, BlockAddedObserver {
private static PandaPrinter INSTANCE;
public static PandaPrinter init(final Optional<Difficulty> currentTotal, final Difficulty ttd) {
if (INSTANCE != null) {
LOG.debug("overwriting already initialized panda printer");
}
INSTANCE = new PandaPrinter(currentTotal, ttd);
return INSTANCE;
}
public static PandaPrinter getInstance() {
if (INSTANCE == null) {
throw new IllegalStateException("Uninitialized, unknown ttd");
}
return INSTANCE;
}
protected PandaPrinter(final Optional<Difficulty> currentTotal, final Difficulty ttd) {
this.ttd = ttd;
if (currentTotal.isPresent() && currentTotal.get().greaterOrEqualThan(ttd)) {
this.readyBeenDisplayed.set(true);
this.ttdBeenDisplayed.set(true);
this.finalizedBeenDisplayed.set(true);
}
}
private static final Logger LOG = LoggerFactory.getLogger(PandaPrinter.class);
private static final String readyBanner = PandaPrinter.loadBanner("/readyPanda.txt");
private static final String ttdBanner = PandaPrinter.loadBanner("/ttdPanda.txt");
private static final String finalizedBanner = PandaPrinter.loadBanner("/finalizedPanda.txt");
private static final AtomicBoolean hasTTD = new AtomicBoolean(false);
private static final AtomicBoolean inSync = new AtomicBoolean(false);
public static final AtomicBoolean readyBeenDisplayed = new AtomicBoolean();
public static final AtomicBoolean ttdBeenDisplayed = new AtomicBoolean();
public static final AtomicBoolean finalizedBeenDisplayed = new AtomicBoolean();
private final Difficulty ttd;
private final AtomicBoolean hasTTD = new AtomicBoolean(false);
private final AtomicBoolean inSync = new AtomicBoolean(false);
private final AtomicBoolean isPoS = new AtomicBoolean(false);
public final AtomicBoolean readyBeenDisplayed = new AtomicBoolean();
public final AtomicBoolean ttdBeenDisplayed = new AtomicBoolean();
public final AtomicBoolean finalizedBeenDisplayed = new AtomicBoolean();
private static String loadBanner(final String filename) {
Class<PandaPrinter> c = PandaPrinter.class;
@ -55,55 +89,56 @@ public class PandaPrinter implements InSyncListener, ForkchoiceMessageListener,
resultStringBuilder.append(line).append("\n");
}
} catch (IOException e) {
LOG.error("Couldn't load hilarious panda banner");
LOG.error("Couldn't load hilarious panda banner at {} ", filename);
}
return resultStringBuilder.toString();
}
public static void hasTTD() {
PandaPrinter.hasTTD.getAndSet(true);
if (hasTTD.get() && inSync.get()) {
printReadyToMerge();
}
public void hasTTD() {
this.hasTTD.getAndSet(true);
}
public static void inSync() {
PandaPrinter.inSync.getAndSet(true);
if (inSync.get() && hasTTD.get()) {
printReadyToMerge();
}
public void inSync() {
this.inSync.getAndSet(true);
}
public static void printOnFirstCrossing() {
public void printOnFirstCrossing() {
if (!ttdBeenDisplayed.get()) {
LOG.info("Crossed TTD, merging underway!");
LOG.info("\n" + ttdBanner);
ttdBeenDisplayed.compareAndSet(false, true);
}
ttdBeenDisplayed.compareAndSet(false, true);
}
static void resetForTesting() {
public void resetForTesting() {
ttdBeenDisplayed.set(false);
readyBeenDisplayed.set(false);
finalizedBeenDisplayed.set(false);
hasTTD.set(false);
isPoS.set(false);
inSync.set(false);
}
public static void printReadyToMerge() {
if (!readyBeenDisplayed.get()) {
public void printReadyToMerge() {
if (!readyBeenDisplayed.get() && !isPoS.get() && inSync.get()) {
LOG.info("Configured for TTD and in sync, still receiving PoW blocks. Ready to merge!");
LOG.info("\n" + readyBanner);
readyBeenDisplayed.compareAndSet(false, true);
}
readyBeenDisplayed.compareAndSet(false, true);
}
public static void printFinalized() {
public void printFinalized() {
if (!finalizedBeenDisplayed.get()) {
LOG.info("Beacon chain finalized, welcome to Proof of Stake Ethereum");
LOG.info("\n" + finalizedBanner);
finalizedBeenDisplayed.compareAndSet(false, true);
}
finalizedBeenDisplayed.compareAndSet(false, true);
}
@Override
public void onInSyncStatusChange(final boolean newSyncStatus) {
if (newSyncStatus && hasTTD.get()) {
inSync.set(newSyncStatus);
if (newSyncStatus && hasTTD.get() && !isPoS.get()) {
printReadyToMerge();
}
}
@ -119,12 +154,22 @@ public class PandaPrinter implements InSyncListener, ForkchoiceMessageListener,
}
@Override
public void mergeStateChanged(
final boolean isPoS,
final Optional<Boolean> priorState,
final Optional<Difficulty> difficultyStoppedAt) {
if (isPoS && priorState.isPresent() && !priorState.get()) { // just crossed from PoW to PoS
printOnFirstCrossing();
public void onBlockAdded(final BlockAddedEvent event) {
if (event.isNewCanonicalHead()) {
Block added = event.getBlock();
if (added.getHeader().getDifficulty().greaterOrEqualThan(this.ttd)) {
this.isPoS.set(true);
if (this.inSync.get()) {
this.printOnFirstCrossing();
}
} else {
this.isPoS.set(false);
if (this.inSync.get()) {
if (!readyBeenDisplayed.get()) {
this.printReadyToMerge();
}
}
}
}
}
}

@ -17,85 +17,121 @@
package org.hyperledger.besu.consensus.merge;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
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.Difficulty;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PandaPrinterTest {
final MergeStateHandler fauxTransitionHandler =
(isPoS, priorState, ttd) -> {
if (isPoS && priorState.filter(prior -> !prior).isPresent())
PandaPrinter.printOnFirstCrossing();
PandaPrinter.getInstance().printOnFirstCrossing();
};
@Test
public void printsPanda() {
PandaPrinter.resetForTesting();
assertThat(PandaPrinter.ttdBeenDisplayed).isFalse();
PandaPrinter.printOnFirstCrossing();
assertThat(PandaPrinter.ttdBeenDisplayed).isTrue();
assertThat(PandaPrinter.readyBeenDisplayed).isFalse();
assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse();
PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(1)), Difficulty.of(BigInteger.TEN));
p.resetForTesting();
assertThat(p.ttdBeenDisplayed).isFalse();
p.printOnFirstCrossing();
assertThat(p.ttdBeenDisplayed).isTrue();
assertThat(p.readyBeenDisplayed).isFalse();
assertThat(p.finalizedBeenDisplayed).isFalse();
}
@Test
public void doesNotPrintAtInit() {
PandaPrinter.resetForTesting();
var mergeContext = new PostMergeContext(Difficulty.ONE);
public void doesNotPrintAtPreMergeInit() {
PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(1)), Difficulty.of(BigInteger.TEN));
var mergeContext = new PostMergeContext(Difficulty.of(BigInteger.TEN));
mergeContext.observeNewIsPostMergeState(fauxTransitionHandler);
assertThat(PandaPrinter.ttdBeenDisplayed).isFalse();
assertThat(p.ttdBeenDisplayed).isFalse();
mergeContext.setIsPostMerge(Difficulty.ONE);
assertThat(PandaPrinter.ttdBeenDisplayed).isFalse();
assertThat(PandaPrinter.readyBeenDisplayed).isFalse();
assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse();
assertThat(p.ttdBeenDisplayed).isFalse();
assertThat(p.readyBeenDisplayed).isFalse();
assertThat(p.finalizedBeenDisplayed).isFalse();
}
@Test
public void printsWhenCrossingOnly() {
PandaPrinter.resetForTesting();
var mergeContext = new PostMergeContext(Difficulty.ONE);
mergeContext.observeNewIsPostMergeState(fauxTransitionHandler);
PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(1)), Difficulty.of(10));
p.inSync();
p.hasTTD();
assertThat(p.ttdBeenDisplayed).isFalse();
p.onBlockAdded(withDifficulty(Difficulty.of(11)));
assertThat(p.ttdBeenDisplayed).isTrue();
assertThat(p.readyBeenDisplayed).isFalse();
assertThat(p.finalizedBeenDisplayed).isFalse();
}
assertThat(PandaPrinter.ttdBeenDisplayed).isFalse();
mergeContext.setIsPostMerge(Difficulty.ZERO);
assertThat(PandaPrinter.ttdBeenDisplayed).isFalse();
mergeContext.setIsPostMerge(Difficulty.ONE);
assertThat(PandaPrinter.ttdBeenDisplayed).isTrue();
assertThat(PandaPrinter.readyBeenDisplayed).isFalse();
assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse();
@Test
public void printsReadyOnStartupInSyncWithPoWTTD() {
PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(1)), Difficulty.of(10));
p.inSync();
p.hasTTD();
p.onBlockAdded(withDifficulty(Difficulty.of(2)));
assertThat(p.readyBeenDisplayed).isTrue();
assertThat(p.ttdBeenDisplayed).isFalse();
assertThat(p.finalizedBeenDisplayed).isFalse();
}
@Test
public void printsReadyOnStartupInSyncWithTTD() {
PandaPrinter.resetForTesting();
PandaPrinter.inSync();
PandaPrinter.hasTTD();
assertThat(PandaPrinter.readyBeenDisplayed).isTrue();
assertThat(PandaPrinter.ttdBeenDisplayed).isFalse();
assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse();
public void noPandasPostTTD() {
PandaPrinter p =
new PandaPrinter(Optional.of(Difficulty.of(11)), Difficulty.of(BigInteger.TEN));
p.inSync();
p.hasTTD();
assertThat(p.readyBeenDisplayed).isTrue();
assertThat(p.ttdBeenDisplayed).isTrue();
assertThat(p.finalizedBeenDisplayed).isTrue();
p.onBlockAdded(withDifficulty(Difficulty.of(11)));
assertThat(p.readyBeenDisplayed).isTrue();
assertThat(p.ttdBeenDisplayed).isTrue();
assertThat(p.finalizedBeenDisplayed).isTrue();
}
@Test
public void printsFinalized() {
PandaPrinter.resetForTesting();
PandaPrinter pandaPrinter = new PandaPrinter();
PandaPrinter p = new PandaPrinter(Optional.of(Difficulty.of(9)), Difficulty.of(BigInteger.TEN));
assertThat(p.finalizedBeenDisplayed).isFalse();
MergeContext mergeContext = new PostMergeContext(Difficulty.ZERO);
mergeContext.addNewForkchoiceMessageListener(pandaPrinter);
mergeContext.addNewForkchoiceMessageListener(p);
mergeContext.fireNewUnverifiedForkchoiceMessageEvent(
Hash.ZERO, Optional.of(Hash.ZERO), Hash.ZERO);
assertThat(PandaPrinter.readyBeenDisplayed).isFalse();
assertThat(PandaPrinter.finalizedBeenDisplayed).isFalse();
assertThat(PandaPrinter.ttdBeenDisplayed).isFalse();
mergeContext.fireNewUnverifiedForkchoiceMessageEvent(
Hash.ZERO, Optional.of(Hash.fromHexStringLenient("0x1337")), Hash.ZERO);
assertThat(PandaPrinter.readyBeenDisplayed).isFalse();
assertThat(PandaPrinter.ttdBeenDisplayed).isFalse();
assertThat(PandaPrinter.finalizedBeenDisplayed).isTrue();
assertThat(p.finalizedBeenDisplayed).isTrue();
}
private BlockAddedEvent withDifficulty(final Difficulty diff) {
BlockBody mockBody = mock(BlockBody.class);
when(mockBody.getTransactions()).thenReturn(new ArrayList<>());
BlockHeader mockHeader = mock(BlockHeader.class);
when(mockHeader.getDifficulty()).thenReturn(diff);
when(mockHeader.getParentHash()).thenReturn(Hash.ZERO);
Block mockBlock = mock(Block.class);
when(mockBlock.getHeader()).thenReturn(mockHeader);
when(mockBlock.getBody()).thenReturn(mockBody);
BlockAddedEvent powArrived =
BlockAddedEvent.createForHeadAdvancement(mockBlock, new ArrayList<>(), new ArrayList<>());
return powArrived;
}
}

Loading…
Cancel
Save