From 3abd8643a2fd2ca4e0960d02abebebbaf834b16b Mon Sep 17 00:00:00 2001 From: Roberto Saltini <38655434+saltiniroberto@users.noreply.github.com> Date: Tue, 16 Oct 2018 17:07:32 +1100 Subject: [PATCH] [NC-1616] Added iBFT NewChainHeadHeader event and related blockchain observer (#59) --- .../consensus/ibft/IbftChainObserver.java | 31 ++++++++ .../pantheon/consensus/ibft/IbftEvents.java | 3 +- .../ibft/ibftevent/NewChainHead.java | 56 +++++++++++++ .../consensus/ibft/IbftChainObserverTest.java | 78 +++++++++++++++++++ .../controller/IbftPantheonController.java | 3 + 5 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftChainObserver.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftevent/NewChainHead.java create mode 100644 consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftChainObserverTest.java diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftChainObserver.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftChainObserver.java new file mode 100644 index 0000000000..d06800cd4f --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftChainObserver.java @@ -0,0 +1,31 @@ +package tech.pegasys.pantheon.consensus.ibft; + +import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead; +import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent; +import tech.pegasys.pantheon.ethereum.chain.BlockAddedObserver; +import tech.pegasys.pantheon.ethereum.chain.Blockchain; + +/** + * Blockchain observer that adds {@link NewChainHead} events to the event queue when a new block is + * added to the chain head + */ +public class IbftChainObserver implements BlockAddedObserver { + private final IbftEventQueue queue; + + public IbftChainObserver(final IbftEventQueue queue) { + this.queue = queue; + } + + @Override + public void onBlockAdded(final BlockAddedEvent event, final Blockchain blockchain) { + switch (event.getEventType()) { + case HEAD_ADVANCED: + queue.add(new NewChainHead(event.getBlock().getHeader())); + break; + + default: + throw new IllegalStateException( + String.format("Unexpected BlockAddedEvent received: %s", event.getEventType())); + } + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftEvents.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftEvents.java index 19aa2a8f27..76f4349486 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftEvents.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftEvents.java @@ -9,6 +9,7 @@ public class IbftEvents { } public enum Type { - ROUND_EXPIRY + ROUND_EXPIRY, + NEW_CHAIN_HEAD_HEADER } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftevent/NewChainHead.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftevent/NewChainHead.java new file mode 100644 index 0000000000..e488959787 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftevent/NewChainHead.java @@ -0,0 +1,56 @@ +package tech.pegasys.pantheon.consensus.ibft.ibftevent; + +import tech.pegasys.pantheon.consensus.ibft.IbftEvent; +import tech.pegasys.pantheon.consensus.ibft.IbftEvents.Type; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; + +import java.util.Objects; + +import com.google.common.base.MoreObjects; + +/** Event indicating that new chain head has been received */ +public final class NewChainHead implements IbftEvent { + private final BlockHeader newChainHeadHeader; + + /** + * Constructor for a NewChainHead event + * + * @param newChainHeadHeader The header of the current blockchain head + */ + public NewChainHead(final BlockHeader newChainHeadHeader) { + this.newChainHeadHeader = newChainHeadHeader; + } + + @Override + public Type getType() { + return Type.NEW_CHAIN_HEAD_HEADER; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("New Chain Head Header", newChainHeadHeader) + .toString(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final NewChainHead that = (NewChainHead) o; + return Objects.equals(newChainHeadHeader, that.newChainHeadHeader); + } + + @Override + public int hashCode() { + return Objects.hash(newChainHeadHeader); + } + + public BlockHeader getNewChainHeadHeader() { + return newChainHeadHeader; + } +} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftChainObserverTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftChainObserverTest.java new file mode 100644 index 0000000000..6bb6a646b3 --- /dev/null +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftChainObserverTest.java @@ -0,0 +1,78 @@ +package tech.pegasys.pantheon.consensus.ibft; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead; +import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent; +import tech.pegasys.pantheon.ethereum.chain.Blockchain; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.core.BlockBody; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; +import tech.pegasys.pantheon.ethereum.core.Hash; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class IbftChainObserverTest { + @Test + public void newChainHeadHeaderEventIsAddedToTheQueue() { + final Blockchain mockBlockchain = mock(Blockchain.class); + final IbftEventQueue mockQueue = mock(IbftEventQueue.class); + final BlockAddedEvent mockBlockAddedEvent = mock(BlockAddedEvent.class); + + final IbftChainObserver ibftChainObserver = new IbftChainObserver(mockQueue); + + final BlockHeader header = + new BlockHeaderTestFixture() + .number(1234) + .coinbase(Address.ECREC) + .parentHash(Hash.EMPTY_LIST_HASH) + .buildHeader(); + + final Block block = new Block(header, new BlockBody(emptyList(), emptyList())); + + when(mockBlockAddedEvent.getEventType()).thenReturn(BlockAddedEvent.EventType.HEAD_ADVANCED); + when(mockBlockAddedEvent.getBlock()).thenReturn(block); + + ibftChainObserver.onBlockAdded(mockBlockAddedEvent, mockBlockchain); + + ArgumentCaptor ibftEventArgumentCaptor = ArgumentCaptor.forClass(IbftEvent.class); + verify(mockQueue).add(ibftEventArgumentCaptor.capture()); + + assertThat(ibftEventArgumentCaptor.getValue() instanceof NewChainHead).isTrue(); + assertThat(((NewChainHead) ibftEventArgumentCaptor.getValue()).getNewChainHeadHeader()) + .isEqualTo(header); + } + + @Test(expected = IllegalStateException.class) + public void exceptionIsThrownWhenEventTypeIsFork() { + final Blockchain mockBlockchain = mock(Blockchain.class); + final IbftEventQueue mockQueue = mock(IbftEventQueue.class); + final BlockAddedEvent mockBlockAddedEvent = mock(BlockAddedEvent.class); + + when(mockBlockAddedEvent.getEventType()).thenReturn(BlockAddedEvent.EventType.FORK); + + final IbftChainObserver ibftChainObserver = new IbftChainObserver(mockQueue); + + ibftChainObserver.onBlockAdded(mockBlockAddedEvent, mockBlockchain); + } + + @Test(expected = IllegalStateException.class) + public void exceptionIsThrownWhenEventTypeIsChainReorg() { + final Blockchain mockBlockchain = mock(Blockchain.class); + final IbftEventQueue mockQueue = mock(IbftEventQueue.class); + final BlockAddedEvent mockBlockAddedEvent = mock(BlockAddedEvent.class); + + when(mockBlockAddedEvent.getEventType()).thenReturn(BlockAddedEvent.EventType.CHAIN_REORG); + + final IbftChainObserver ibftChainObserver = new IbftChainObserver(mockQueue); + + ibftChainObserver.onBlockAdded(mockBlockAddedEvent, mockBlockchain); + } +} diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java index 09eadc689e..cce0ddf887 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java @@ -5,6 +5,7 @@ import static org.apache.logging.log4j.LogManager.getLogger; import tech.pegasys.pantheon.consensus.common.EpochManager; import tech.pegasys.pantheon.consensus.common.VoteProposer; import tech.pegasys.pantheon.consensus.common.VoteTally; +import tech.pegasys.pantheon.consensus.ibft.IbftChainObserver; import tech.pegasys.pantheon.consensus.ibft.IbftContext; import tech.pegasys.pantheon.consensus.ibft.IbftEventQueue; import tech.pegasys.pantheon.consensus.ibft.IbftProcessor; @@ -145,6 +146,8 @@ public class IbftPantheonController implements PantheonController