diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java index 0c20221fa9..1a638b2326 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; -import org.hyperledger.besu.ethereum.eth.manager.ForkIdManager.ForkId; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.StatusMessage; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; @@ -271,7 +270,7 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver { } final Capability cap = connection.capability(getSupportedProtocol()); - final ForkId latestForkId = cap.getVersion() >= 64 ? forkIdManager.getLatestForkId() : null; + final ForkId latestForkId = cap.getVersion() >= 64 ? forkIdManager.computeForkId() : null; // TODO: look to consolidate code below if possible // making status non-final and implementing it above would be one way. final StatusMessage status = diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkId.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkId.java new file mode 100644 index 0000000000..ce00373054 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkId.java @@ -0,0 +1,96 @@ +/* + * 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.manager; + +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; +import org.hyperledger.besu.util.EndianUtils; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; + +public class ForkId { + final Bytes hash; + final Bytes next; + Bytes forkIdRLP; + + protected ForkId(final Bytes hash, final Bytes next) { + this.hash = hash; + this.next = next; + createForkIdRLP(); + } + + public ForkId(final Bytes hash, final long next) { + this(hash, Bytes.wrap(EndianUtils.longToBigEndian(next)).trimLeadingZeros()); + } + + public long getNext() { + return next.toLong(); + } + + public Bytes getHash() { + return hash; + } + + void createForkIdRLP() { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + writeTo(out); + forkIdRLP = out.encoded(); + } + + public void writeTo(final RLPOutput out) { + out.startList(); + out.writeBytes(hash); + out.writeBytes(next); + out.endList(); + } + + public static ForkId readFrom(final RLPInput in) { + in.enterList(); + final Bytes hash = in.readBytes(); + final long next = in.readLongScalar(); + in.leaveList(); + return new ForkId(hash, next); + } + + public List asList() { + final ArrayList forRLP = new ArrayList<>(); + forRLP.add(this); + return forRLP; + } + + @Override + public String toString() { + return "ForkId(hash=" + this.hash + ", next=" + next.toLong() + ")"; + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof ForkId) { + final ForkId other = (ForkId) obj; + final long thisNext = next.toLong(); + return other.getHash().equals(this.hash) && thisNext == other.getNext(); + } + return false; + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManager.java index b6b23dce22..79723feec3 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManager.java @@ -15,22 +15,19 @@ package org.hyperledger.besu.ethereum.eth.manager; import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Collections.singletonList; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Hash; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.RLPInput; -import org.hyperledger.besu.ethereum.rlp.RLPOutput; +import org.hyperledger.besu.util.EndianUtils; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; +import java.util.function.LongSupplier; import java.util.stream.Collectors; import java.util.zip.CRC32; +import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -39,69 +36,47 @@ public class ForkIdManager { private final Hash genesisHash; private final List forkAndHashList; - private final List> forkIDCheckers; + private final List forks; + private final LongSupplier chainHeadSupplier; + private final long forkNext; + private final boolean onlyZerosForkBlocks; + private final long highestKnownFork; + private Bytes genesisHashCrc; - public ForkIdManager(final Blockchain blockchain, final List forks) { + public ForkIdManager(final Blockchain blockchain, final List nonFilteredForks) { checkNotNull(blockchain); - checkNotNull(forks); + checkNotNull(nonFilteredForks); + this.chainHeadSupplier = blockchain::getChainHeadBlockNumber; this.genesisHash = blockchain.getGenesisBlock().getHash(); this.forkAndHashList = new ArrayList<>(); - final Predicate legacyForkIdChecker = - createForkIDChecker( - blockchain, - genesisHash, - forks, - fs -> - fs.stream() - .filter(fork -> fork > 0) - .distinct() - .collect(Collectors.toUnmodifiableList()), - forkAndHashList); - // if the fork list contains only zeros then we may be in a consortium/dev network - if (onlyZerosForkBlocks(forks)) { - this.forkIDCheckers = singletonList(forkId -> true); - } else { - final Predicate newForkIdChecker = - createForkIDChecker( - blockchain, - genesisHash, - forks, - fs -> fs.stream().distinct().collect(Collectors.toUnmodifiableList()), - new ArrayList<>()); - this.forkIDCheckers = Arrays.asList(newForkIdChecker, legacyForkIdChecker); + this.forks = + nonFilteredForks.stream() + .filter(fork -> fork > 0) + .distinct() + .sorted() + .collect(Collectors.toUnmodifiableList()); + this.onlyZerosForkBlocks = nonFilteredForks.stream().allMatch(value -> 0L == value); + this.forkNext = createForkIds(); + this.highestKnownFork = !forks.isEmpty() ? forks.get(forks.size() - 1) : 0L; + } + + public ForkId computeForkId() { + final long head = chainHeadSupplier.getAsLong(); + for (final ForkId forkId : forkAndHashList) { + if (head < forkId.getNext()) { + return forkId; + } } + return forkAndHashList.isEmpty() + ? new ForkId(genesisHashCrc, 0) + : forkAndHashList.get(forkAndHashList.size() - 1); } - private static Predicate createForkIDChecker( - final Blockchain blockchain, - final Hash genesisHash, - final List forks, - final Function, List> sanitizer, - final List forkIds) { - final List sanitizedForks = sanitizer.apply(forks); - final long forkNext = createForkIds(genesisHash, sanitizedForks, forkIds); - return eip2124(blockchain, forkNext, forkIds, highestKnownFork(sanitizedForks)); - } - - private static boolean onlyZerosForkBlocks(final List forks) { - return forks.stream().allMatch(value -> 0L == value); - } - - private static long highestKnownFork(final List forks) { - return !forks.isEmpty() ? forks.get(forks.size() - 1) : 0L; - } - - public List getForkAndHashList() { + @VisibleForTesting + List getForkAndHashList() { return this.forkAndHashList; } - ForkId getLatestForkId() { - if (forkAndHashList.size() > 0) { - return forkAndHashList.get(forkAndHashList.size() - 1); - } - return null; - } - public static ForkId readFrom(final RLPInput in) { in.enterList(); final Bytes hash = in.readBytes(); @@ -117,50 +92,33 @@ public class ForkIdManager { * @return boolean (peer valid (true) or invalid (false)) */ boolean peerCheck(final ForkId forkId) { - return forkIDCheckers.stream().anyMatch(checker -> checker.test(forkId)); + if (forkId == null || onlyZerosForkBlocks) { + return true; // Another method must be used to validate (i.e. genesis hash) + } + // Run the fork checksum validation rule set: + // 1. If local and remote FORK_CSUM matches, connect. + // The two nodes are in the same fork state currently. They might know + // of differing future forks, but that's not relevant until the fork + // triggers (might be postponed, nodes might be updated to match). + // 2. If the remote FORK_CSUM is a subset of the local past forks and the + // remote FORK_NEXT matches with the locally following fork block number, + // connect. + // Remote node is currently syncing. It might eventually diverge from + // us, but at this current point in time we don't have enough information. + // 3. If the remote FORK_CSUM is a superset of the local past forks and can + // be completed with locally known future forks, connect. + // Local node is currently syncing. It might eventually diverge from + // the remote, but at this current point in time we don't have enough + // information. + // 4. Reject in all other cases. + if (!isHashKnown(forkId.getHash())) { + return false; + } + return chainHeadSupplier.getAsLong() < forkNext + || (isForkKnown(forkId.getNext()) + && isRemoteAwareOfPresent(forkId.getHash(), forkId.getNext())); } - private static Predicate eip2124( - final Blockchain blockchain, - final long forkNext, - final List forkAndHashList, - final long highestKnownFork) { - return forkId -> { - if (forkId == null) { - return true; // Another method must be used to validate (i.e. genesis hash) - } - // Run the fork checksum validation rule set: - // 1. If local and remote FORK_CSUM matches, connect. - // The two nodes are in the same fork state currently. They might know - // of differing future forks, but that's not relevant until the fork - // triggers (might be postponed, nodes might be updated to match). - // 2. If the remote FORK_CSUM is a subset of the local past forks and the - // remote FORK_NEXT matches with the locally following fork block number, - // connect. - // Remote node is currently syncing. It might eventually diverge from - // us, but at this current point in time we don't have enough information. - // 3. If the remote FORK_CSUM is a superset of the local past forks and can - // be completed with locally known future forks, connect. - // Local node is currently syncing. It might eventually diverge from - // the remote, but at this current point in time we don't have enough - // information. - // 4. Reject in all other cases. - if (isHashKnown(forkId.getHash(), forkAndHashList)) { - if (blockchain.getChainHeadBlockNumber() < forkNext) { - return true; - } else { - if (isForkKnown(forkId.getNext(), highestKnownFork, forkAndHashList)) { - return isRemoteAwareOfPresent( - forkId.getHash(), forkId.getNext(), highestKnownFork, forkAndHashList); - } else { - return false; - } - } - } else { - return false; - } - }; - } /** * Non EIP-2124 behaviour * @@ -171,21 +129,16 @@ public class ForkIdManager { return !peerGenesisHash.equals(genesisHash); } - private static boolean isHashKnown(final Bytes forkHash, final List forkAndHashList) { + private boolean isHashKnown(final Bytes forkHash) { return forkAndHashList.stream().map(ForkId::getHash).anyMatch(hash -> hash.equals(forkHash)); } - private static boolean isForkKnown( - final Long nextFork, final long highestKnownFork, final List forkAndHashList) { + private boolean isForkKnown(final Long nextFork) { return highestKnownFork < nextFork || forkAndHashList.stream().map(ForkId::getNext).anyMatch(fork -> fork.equals(nextFork)); } - private static boolean isRemoteAwareOfPresent( - final Bytes forkHash, - final Long nextFork, - final long highestKnownFork, - final List forkAndHashList) { + private boolean isRemoteAwareOfPresent(final Bytes forkHash, final Long nextFork) { for (final ForkId j : forkAndHashList) { if (forkHash.equals(j.getHash())) { if (nextFork.equals(j.getNext())) { @@ -200,121 +153,35 @@ public class ForkIdManager { return false; } - private static long createForkIds( - final Hash genesisHash, final List forks, final List forkIds) { + private long createForkIds() { final CRC32 crc = new CRC32(); crc.update(genesisHash.toArray()); - final List forkHashes = new ArrayList<>(List.of(getCurrentCrcHash(crc))); - for (final Long fork : forks) { - updateCrc(crc, fork); - forkHashes.add(getCurrentCrcHash(crc)); - } + genesisHashCrc = getCurrentCrcHash(crc); + final List forkHashes = new ArrayList<>(List.of(genesisHashCrc)); + forks.forEach( + fork -> { + updateCrc(crc, fork); + forkHashes.add(getCurrentCrcHash(crc)); + }); + // This loop is for all the fork hashes that have an associated "next fork" for (int i = 0; i < forks.size(); i++) { - forkIds.add(new ForkId(forkHashes.get(i), forks.get(i))); + forkAndHashList.add(new ForkId(forkHashes.get(i), forks.get(i))); } long forkNext = 0; if (!forks.isEmpty()) { - forkNext = forkIds.get(forkIds.size() - 1).getNext(); - forkIds.add(new ForkId(forkHashes.get(forkHashes.size() - 1), 0)); + forkNext = forkAndHashList.get(forkAndHashList.size() - 1).getNext(); + forkAndHashList.add(new ForkId(forkHashes.get(forkHashes.size() - 1), 0)); } return forkNext; } private static void updateCrc(final CRC32 crc, final Long block) { - final byte[] byteRepresentationFork = longToBigEndian(block); + final byte[] byteRepresentationFork = EndianUtils.longToBigEndian(block); crc.update(byteRepresentationFork, 0, byteRepresentationFork.length); } private static Bytes getCurrentCrcHash(final CRC32 crc) { return Bytes.ofUnsignedInt(crc.getValue()); } - - public static class ForkId { - final Bytes hash; - final Bytes next; - Bytes forkIdRLP; - - private ForkId(final Bytes hash, final Bytes next) { - this.hash = hash; - this.next = next; - createForkIdRLP(); - } - - public ForkId(final Bytes hash, final long next) { - this(hash, Bytes.wrap(longToBigEndian(next)).trimLeadingZeros()); - } - - public long getNext() { - return next.toLong(); - } - - public Bytes getHash() { - return hash; - } - - void createForkIdRLP() { - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - writeTo(out); - forkIdRLP = out.encoded(); - } - - public void writeTo(final RLPOutput out) { - out.startList(); - out.writeBytes(hash); - out.writeBytes(next); - out.endList(); - } - - public static ForkId readFrom(final RLPInput in) { - in.enterList(); - final Bytes hash = in.readBytes(); - final long next = in.readLongScalar(); - in.leaveList(); - return new ForkId(hash, next); - } - - public List asList() { - final ArrayList forRLP = new ArrayList<>(); - forRLP.add(this); - return forRLP; - } - - @Override - public String toString() { - return "ForkId(hash=" + this.hash + ", next=" + next.toLong() + ")"; - } - - @Override - public boolean equals(final Object obj) { - if (obj instanceof ForkId) { - final ForkId other = (ForkId) obj; - final long thisNext = next.toLong(); - return other.getHash().equals(this.hash) && thisNext == other.getNext(); - } - return false; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - } - - // next two methods adopted from: - // https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/util/Pack.java - private static byte[] longToBigEndian(final long n) { - final byte[] bs = new byte[8]; - intToBigEndian((int) (n >>> 32), bs, 0); - intToBigEndian((int) (n & 0xffffffffL), bs, 4); - return bs; - } - - @SuppressWarnings("MethodInputParametersMustBeFinal") - private static void intToBigEndian(final int n, final byte[] bs, int off) { - bs[off] = (byte) (n >>> 24); - bs[++off] = (byte) (n >>> 16); - bs[++off] = (byte) (n >>> 8); - bs[++off] = (byte) (n); - } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessage.java index c3847878cf..f8691baee7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessage.java @@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.eth.messages; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Hash; -import org.hyperledger.besu.ethereum.eth.manager.ForkIdManager; +import org.hyperledger.besu.ethereum.eth.manager.ForkId; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractMessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; @@ -57,7 +57,7 @@ public final class StatusMessage extends AbstractMessageData { final Difficulty totalDifficulty, final Hash bestHash, final Hash genesisHash, - final ForkIdManager.ForkId forkId) { + final ForkId forkId) { final EthStatus status = new EthStatus(protocolVersion, networkId, totalDifficulty, bestHash, genesisHash, forkId); final BytesValueRLPOutput out = new BytesValueRLPOutput(); @@ -111,7 +111,7 @@ public final class StatusMessage extends AbstractMessageData { } /** @return The fork id of the network the associated node is participating in. */ - public ForkIdManager.ForkId forkId() { + public ForkId forkId() { return status().forkId; } @@ -129,7 +129,7 @@ public final class StatusMessage extends AbstractMessageData { private final Difficulty totalDifficulty; private final Hash bestHash; private final Hash genesisHash; - private final ForkIdManager.ForkId forkId; + private final ForkId forkId; EthStatus( final int protocolVersion, @@ -151,7 +151,7 @@ public final class StatusMessage extends AbstractMessageData { final Difficulty totalDifficulty, final Hash bestHash, final Hash genesisHash, - final ForkIdManager.ForkId forkHash) { + final ForkId forkHash) { this.protocolVersion = protocolVersion; this.networkId = networkId; this.totalDifficulty = totalDifficulty; @@ -182,9 +182,9 @@ public final class StatusMessage extends AbstractMessageData { final Difficulty totalDifficulty = Difficulty.of(in.readUInt256Scalar()); final Hash bestHash = Hash.wrap(in.readBytes32()); final Hash genesisHash = Hash.wrap(in.readBytes32()); - final ForkIdManager.ForkId forkId; + final ForkId forkId; if (in.nextIsList()) { - forkId = ForkIdManager.ForkId.readFrom(in); + forkId = ForkId.readFrom(in); } else { forkId = null; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EIP2124Test.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EIP2124Test.java new file mode 100644 index 0000000000..dacc7b53b3 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EIP2124Test.java @@ -0,0 +1,735 @@ +/* + * 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.manager; + +import static com.google.common.primitives.Longs.asList; +import static java.util.Optional.empty; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Hash; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class EIP2124Test { + private static final Logger LOG = LogManager.getLogger(); + + @Parameters(name = "{index}: {0}") + public static Collection data() { + return Arrays.asList( + new Object[][] { + // Mainnet test cases + { + "Mainnet // Unsynced", + Network.MAINNET, + 0L, + wantForkId("0xfc64ec04", 1150000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // First Homestead block", + Network.MAINNET, + 1150000L, + wantForkId("0x97c2c34c", 1920000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // Last Homestead block", + Network.MAINNET, + 1919999L, + wantForkId("0x97c2c34c", 1920000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // First DAO block", + Network.MAINNET, + 1920000L, + wantForkId("0x91d1f948", 2463000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // Last DAO block", + Network.MAINNET, + 2462999L, + wantForkId("0x91d1f948", 2463000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // First Tangerine block", + Network.MAINNET, + 2463000L, + wantForkId("0x7a64da13", 2675000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // Last Tangerine block", + Network.MAINNET, + 2674999L, + wantForkId("0x7a64da13", 2675000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // First Spurious block", + Network.MAINNET, + 2675000L, + wantForkId("0x3edd5b10", 4370000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // Last Spurious block", + Network.MAINNET, + 4369999L, + wantForkId("0x3edd5b10", 4370000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // First Byzantium block", + Network.MAINNET, + 4370000L, + wantForkId("0xa00bc324", 7280000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // Last Byzantium block", + Network.MAINNET, + 7279999L, + wantForkId("0xa00bc324", 7280000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // First and last Constantinople, first Petersburg block", + Network.MAINNET, + 7280000L, + wantForkId("0x668db0af", 9069000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // Last Petersburg block", + Network.MAINNET, + 9068999L, + wantForkId("0x668db0af", 9069000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // First Istanbul and first Muir Glacier block", + Network.MAINNET, + 9069000L, + wantForkId("0x879d6e30", 9200000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // Last Istanbul and first Muir Glacier block", + Network.MAINNET, + 9199999L, + wantForkId("0x879d6e30", 9200000L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // First Muir Glacier block", + Network.MAINNET, + 9200000L, + wantForkId("0xe029e991", 0L), + Optional.of(ForkIds.MAINNET), + empty() + }, + { + "Mainnet // Future Muir Glacier block", + Network.MAINNET, + 10000000L, + wantForkId("0xe029e991", 0L), + Optional.of(ForkIds.MAINNET), + empty() + }, + // Ropsten test cases + { + "Ropsten // Unsynced, last Frontier, Homestead and first Tangerine block", + Network.ROPSTEN, + 0L, + wantForkId("0x30c7ddbc", 10L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // Last Tangerine block", + Network.ROPSTEN, + 9L, + wantForkId("0x30c7ddbc", 10L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // First Spurious block", + Network.ROPSTEN, + 10L, + wantForkId("0x63760190", 1700000L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // Last Spurious block", + Network.ROPSTEN, + 1699999L, + wantForkId("0x63760190", 1700000L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // First Byzantium block", + Network.ROPSTEN, + 1700000L, + wantForkId("0x3ea159c7", 4230000L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // First Byzantium block", + Network.ROPSTEN, + 4229999L, + wantForkId("0x3ea159c7", 4230000L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // First Constantinople block", + Network.ROPSTEN, + 4230000L, + wantForkId("0x97b544f3", 4939394L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // Last Constantinople block", + Network.ROPSTEN, + 4939393L, + wantForkId("0x97b544f3", 4939394L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // First Petersburg block", + Network.ROPSTEN, + 4939394L, + wantForkId("0xd6e2149b", 6485846L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // Last Petersburg block", + Network.ROPSTEN, + 6485845L, + wantForkId("0xd6e2149b", 6485846L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // First Istanbul block", + Network.ROPSTEN, + 6485846L, + wantForkId("0x4bc66396", 7117117L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // Last Istanbul block", + Network.ROPSTEN, + 7117116L, + wantForkId("0x4bc66396", 7117117L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // First Muir Glacier block", + Network.ROPSTEN, + 7117117L, + wantForkId("0x6727ef90", 0L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + { + "Ropsten // Future", + Network.ROPSTEN, + 7500000L, + wantForkId("0x6727ef90", 0L), + Optional.of(ForkIds.ROPSTEN), + empty() + }, + // Rinkeby test cases + { + "Rinkeby // Unsynced, last Frontier block", + Network.RINKEBY, + 0L, + wantForkId("0x3b8e0691", 1L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // First and last Homestead block", + Network.RINKEBY, + 1L, + wantForkId("0x60949295", 2L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // First and last Tangerine block", + Network.RINKEBY, + 2L, + wantForkId("0x8bde40dd", 3L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // First Spurious block", + Network.RINKEBY, + 3L, + wantForkId("0xcb3a64bb", 1035301L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // Last Spurious block", + Network.RINKEBY, + 1035300L, + wantForkId("0xcb3a64bb", 1035301L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // First Byzantium block", + Network.RINKEBY, + 1035301L, + wantForkId("0x8d748b57", 3660663L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // Last Byzantium block", + Network.RINKEBY, + 3660662L, + wantForkId("0x8d748b57", 3660663L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // First Constantinople block", + Network.RINKEBY, + 3660663L, + wantForkId("0xe49cab14", 4321234L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // Last Constantinople block", + Network.RINKEBY, + 4321233L, + wantForkId("0xe49cab14", 4321234L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // First Petersburg block", + Network.RINKEBY, + 4321234L, + wantForkId("0xafec6b27", 5435345L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // Last Petersburg block", + Network.RINKEBY, + 5435344L, + wantForkId("0xafec6b27", 5435345L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // First Istanbul block", + Network.RINKEBY, + 5435345L, + wantForkId("0xcbdb8838", 0L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + { + "Rinkeby // Future Istanbul block", + Network.RINKEBY, + 6000000L, + wantForkId("0xcbdb8838", 0L), + Optional.of(ForkIds.RINKEBY), + empty() + }, + // Goerli test cases + { + "Goerli // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block", + Network.GOERLI, + 0L, + wantForkId("0xa3f5ab08", 1561651L), + Optional.of(ForkIds.GOERLI), + empty() + }, + { + "Goerli // Last Petersburg block", + Network.GOERLI, + 1561650L, + wantForkId("0xa3f5ab08", 1561651L), + Optional.of(ForkIds.GOERLI), + empty() + }, + { + "Goerli // First Istanbul block", + Network.GOERLI, + 1561651L, + wantForkId("0xc25efa5c", 0L), + Optional.of(ForkIds.GOERLI), + empty() + }, + { + "Goerli // Future Istanbul block", + Network.GOERLI, + 2000000L, + wantForkId("0xc25efa5c", 0L), + Optional.of(ForkIds.GOERLI), + empty() + }, + // Private network test cases + { + "Private // Unsynced", + Network.PRIVATE, + 0L, + wantForkId("0x190a55ad", 0L), + empty(), + empty() + }, + { + "Private // First block", + Network.PRIVATE, + 1L, + wantForkId("0x190a55ad", 0L), + empty(), + empty() + }, + { + "Private // Future block", + Network.PRIVATE, + 1000000L, + wantForkId("0x190a55ad", 0L), + empty(), + empty() + }, + // Peer check cases + { + "check1PetersburgWithRemoteAnnouncingTheSame", + Network.MAINNET, + 7987396L, + empty(), + empty(), + wantPeerCheck("0x668db0af", 0L, true) + }, + { + "check2PetersburgWithRemoteAnnouncingTheSameAndNextFork", + Network.MAINNET, + 7987396L, + empty(), + empty(), + wantPeerCheck("0x668db0af", Long.MAX_VALUE, true) + }, + { + "check3ByzantiumAwareOfPetersburgRemoteUnawareOfPetersburg", + Network.MAINNET, + 7279999L, + empty(), + empty(), + wantPeerCheck("0xa00bc324", 0L, true) + }, + { + "check4ByzantiumAwareOfPetersburgRemoteAwareOfPetersburg", + Network.MAINNET, + 7987396L, + empty(), + empty(), + wantPeerCheck("0xa00bc324", 7280000L, true) + }, + { + "check5ByzantiumAwareOfPetersburgRemoteAnnouncingUnknownFork", + Network.MAINNET, + 7279999L, + empty(), + empty(), + wantPeerCheck("0xa00bc324", Long.MAX_VALUE, true) + }, + { + "check6PetersburgWithRemoteAnnouncingByzantiumAwareOfPetersburg", + Network.MAINNET, + 7987396L, + empty(), + empty(), + wantPeerCheck("0x668db0af", 7280000L, true) + }, + { + "check7PetersburgWithRemoteAnnouncingSpuriousAwareOfByzantiumRemoteMayNeedUpdate", + Network.MAINNET, + 7987396L, + empty(), + empty(), + wantPeerCheck("0x3edd5b10", 4370000L, true) + }, + { + "check8ByzantiumWithRemoteAnnouncingPetersburgLocalOutOfSync", + Network.MAINNET, + 727999L, + empty(), + empty(), + wantPeerCheck("0x668db0af", 0L, true) + }, + { + "check9SpuriousWithRemoteAnnouncingByzantiumRemoteUnawareOfPetersburg", + Network.MAINNET, + 4369999L, + empty(), + empty(), + wantPeerCheck("0xa00bc324", 0L, true) + }, + { + "check10PetersburgWithRemoteAnnouncingByzantiumRemoteUnawareOfAdditionalForks", + Network.network( + GenesisHash.MAINNET, + asList(1150000L, 1920000L, 2463000L, 2675000L, 4370000L, 7280000L)), + 7987396L, + empty(), + empty(), + wantPeerCheck("0xa00bc324", 0L, false) + }, + { + "check11PetersburgWithRemoteAnnouncingPetersburgAndFutureForkLocalNeedsUpdate", + Network.network( + GenesisHash.MAINNET, + asList(1150000L, 1920000L, 2463000L, 2675000L, 4370000L, 7280000L)), + 7987396L, + empty(), + empty(), + wantPeerCheck("0x5cddc0e1", 0L, false) + }, + { + "check12ByzantiumWithRemoteAnnouncingPetersburgAndFutureForkLocalNeedsUpdate", + Network.network( + GenesisHash.MAINNET, + asList(1150000L, 1920000L, 2463000L, 2675000L, 4370000L, 7280000L)), + 7279999L, + empty(), + empty(), + wantPeerCheck("0x5cddc0e1", 0L, false) + }, + { + "check13ByzantiumWithRemoteAnnouncingRinkebyPetersburg", + Network.network( + GenesisHash.MAINNET, + asList(1150000L, 1920000L, 2463000L, 2675000L, 4370000L, 7280000L)), + 7987396L, + empty(), + empty(), + wantPeerCheck("0xafec6b27", 0L, false) + } + }); + } + + private final String name; + private final Network network; + private final long head; + private final Optional wantForkId; + private final Optional> wantForkIds; + private final Optional wantPeerCheckCase; + + @Test + public void test() { + LOG.info("Running test case {}", name); + final ForkIdManager forkIdManager = + new ForkIdManager(mockBlockchain(network.hash, head), network.forks); + wantForkId.ifPresent(forkId -> assertThat(forkIdManager.computeForkId()).isEqualTo(forkId)); + wantForkIds.ifPresent( + forkIds -> + assertThat(forkIdManager.getForkAndHashList()).containsExactlyElementsOf(forkIds)); + wantPeerCheckCase.ifPresent( + peerCheckCase -> + assertThat( + forkIdManager.peerCheck( + new ForkId( + Bytes.fromHexString(peerCheckCase.forkIdHash), + peerCheckCase.forkIdNext))) + .isEqualTo(peerCheckCase.want)); + } + + public EIP2124Test( + final String name, + final Network network, + final long head, + final Optional wantForkId, + final Optional> wantForkIds, + final Optional wantPeerCheckCase) { + this.name = name; + this.network = network; + this.head = head; + this.wantForkId = wantForkId; + this.wantForkIds = wantForkIds; + this.wantPeerCheckCase = wantPeerCheckCase; + } + + private static Blockchain mockBlockchain(final String genesisHash, final long chainHeight) { + final Blockchain mockchain = mock(Blockchain.class); + final BlockHeader mockHeader = mock(BlockHeader.class); + final Block block = new Block(mockHeader, null); + when(mockchain.getGenesisBlock()).thenReturn(block); + when(mockchain.getChainHeadBlockNumber()).thenReturn(chainHeight); + when(mockHeader.getHash()).thenReturn(Hash.fromHexString(genesisHash)); + return mockchain; + } + + private static class GenesisHash { + private static final String MAINNET = + "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; + private static final String ROPSTEN = + "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"; + private static final String RINKEBY = + "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177"; + private static final String GOERLI = + "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"; + private static final String PRIVATE = + "0x0000000000000000000000000000000000000000000000000000000000000000"; + } + + private static class Forks { + private static final List MAINNET = + Arrays.asList( + 1920000L, 1150000L, 2463000L, 2675000L, 2675000L, 4370000L, 7280000L, 7280000L, + 9069000L, 9200000L); + private static final List ROPSTEN = + Arrays.asList(0L, 0L, 10L, 1700000L, 4230000L, 4939394L, 6485846L, 7117117L); + private static final List RINKEBY = + Arrays.asList(1L, 2L, 3L, 3L, 1035301L, 3660663L, 4321234L, 5435345L); + private static final List GOERLI = Arrays.asList(0L, 0L, 0L, 0L, 0L, 0L, 0L, 1561651L); + private static final List PRIVATE = Arrays.asList(0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L); + } + + private static class ForkIds { + private static final List MAINNET = + Arrays.asList( + new ForkId(Bytes.fromHexString("0xfc64ec04"), 1150000L), // Unsynced + new ForkId(Bytes.fromHexString("0x97c2c34c"), 1920000L), // First Homestead block + new ForkId(Bytes.fromHexString("0x91d1f948"), 2463000L), // First DAO block + new ForkId(Bytes.fromHexString("0x7a64da13"), 2675000L), // First Tangerine block + new ForkId(Bytes.fromHexString("0x3edd5b10"), 4370000L), // First Spurious block + new ForkId(Bytes.fromHexString("0xa00bc324"), 7280000L), // First Byzantium block + new ForkId(Bytes.fromHexString("0x668db0af"), 9069000L), + new ForkId(Bytes.fromHexString("0x879d6e30"), 9200000L), + new ForkId(Bytes.fromHexString("0xe029e991"), 0L)); + private static final List ROPSTEN = + Arrays.asList( + new ForkId(Bytes.fromHexString("0x30c7ddbc"), 10L), + new ForkId(Bytes.fromHexString("0x63760190"), 1700000L), + new ForkId(Bytes.fromHexString("0x3ea159c7"), 4230000L), + new ForkId(Bytes.fromHexString("0x97b544f3"), 4939394L), + new ForkId(Bytes.fromHexString("0xd6e2149b"), 6485846L), + new ForkId(Bytes.fromHexString("0x4bc66396"), 7117117L), + new ForkId(Bytes.fromHexString("0x6727ef90"), 0L)); + private static final List RINKEBY = + Arrays.asList( + new ForkId(Bytes.fromHexString("0x3b8e0691"), 1L), + new ForkId(Bytes.fromHexString("0x60949295"), 2L), + new ForkId(Bytes.fromHexString("0x8bde40dd"), 3L), + new ForkId(Bytes.fromHexString("0xcb3a64bb"), 1035301L), + new ForkId(Bytes.fromHexString("0x8d748b57"), 3660663L), + new ForkId(Bytes.fromHexString("0xe49cab14"), 4321234L), + new ForkId(Bytes.fromHexString("0xafec6b27"), 5435345L), + new ForkId(Bytes.fromHexString("0xcbdb8838"), 0L)); + private static final List GOERLI = + Arrays.asList( + new ForkId(Bytes.fromHexString("0xa3f5ab08"), 1561651L), + new ForkId(Bytes.fromHexString("0xc25efa5c"), 0L)); + } + + private static class Network { + private static final Network MAINNET = network(GenesisHash.MAINNET, Forks.MAINNET); + private static final Network ROPSTEN = network(GenesisHash.ROPSTEN, Forks.ROPSTEN); + private static final Network RINKEBY = network(GenesisHash.RINKEBY, Forks.RINKEBY); + private static final Network GOERLI = network(GenesisHash.GOERLI, Forks.GOERLI); + private static final Network PRIVATE = network(GenesisHash.PRIVATE, Forks.PRIVATE); + private final String hash; + private final List forks; + + Network(final String hash, final List forks) { + this.hash = hash; + this.forks = forks; + } + + private static Network network(final String hash, final List forks) { + return new Network(hash, forks); + } + } + + private static class PeerCheckCase { + private final String forkIdHash; + private final long forkIdNext; + private final boolean want; + + private PeerCheckCase(final String forkIdHash, final long forkIdNext, final boolean want) { + this.forkIdHash = forkIdHash; + this.forkIdNext = forkIdNext; + this.want = want; + } + } + + private static ForkId forkId(final String hash, final long next) { + return new ForkId(Bytes.fromHexString(hash), next); + } + + private static Optional wantForkId(final String hash, final long next) { + return Optional.of(forkId(hash, next)); + } + + private static Optional wantPeerCheck( + final String hash, final long next, final boolean want) { + return Optional.of(new PeerCheckCase(hash, next, want)); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManagerTest.java deleted file mode 100644 index 3547d36652..0000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManagerTest.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * 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.manager; - -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Hash; -import org.hyperledger.besu.ethereum.eth.manager.ForkIdManager.ForkId; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; - -import java.util.Arrays; -import java.util.List; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; - -public class ForkIdManagerTest { - private final Long[] forksMainnet = {1150000L, 1920000L, 2463000L, 2675000L, 4370000L, 7280000L}; - private final String mainnetGenHash = - "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; - private final String consortiumNetworkGenHash = - "0x4109c6d17ca107e4de7565c94b429db8f5839593a9c57f3f31430b29b378b39d"; - - private Blockchain mockBlockchain(final String genesisHash, final long chainHeight) { - final Blockchain mockchain = mock(Blockchain.class); - final BlockHeader mockHeader = mock(BlockHeader.class); - final Block block = new Block(mockHeader, null); - when(mockchain.getGenesisBlock()).thenReturn(block); - when(mockchain.getChainHeadBlockNumber()).thenReturn(chainHeight); - when(mockHeader.getHash()).thenReturn(Hash.fromHexString(genesisHash)); - return mockchain; - } - - @Test - public void checkItFunctionsWithPresentBehavior() { - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 0), emptyList()); - assertThat(forkIdManager.peerCheck(Hash.fromHexString(mainnetGenHash))).isFalse(); - assertThat(forkIdManager.getLatestForkId()).isNull(); - } - - @Test - public void checkCorrectMainnetForkIdHashesGenerated() { - final ForkId[] checkIds = { - new ForkId(Bytes.fromHexString("0xfc64ec04"), 1150000L), // Unsynced - new ForkId(Bytes.fromHexString("0x97c2c34c"), 1920000L), // First Homestead block - new ForkId(Bytes.fromHexString("0x91d1f948"), 2463000L), // First DAO block - new ForkId(Bytes.fromHexString("0x7a64da13"), 2675000L), // First Tangerine block - new ForkId(Bytes.fromHexString("0x3edd5b10"), 4370000L), // First Spurious block - new ForkId(Bytes.fromHexString("0xa00bc324"), 7280000L), // First Byzantium block - new ForkId(Bytes.fromHexString("0x668db0af"), 0L) // Today Petersburg block - }; - final List list = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = new ForkIdManager(mockBlockchain(mainnetGenHash, 0), list); - final List entries = forkIdManager.getForkAndHashList(); - assertThat(entries).containsExactly(checkIds); - assertThat(forkIdManager.getLatestForkId()).isNotNull(); - assertThat(forkIdManager.getLatestForkId()).isEqualTo(checkIds[6]); - } - - @Test - public void checkCorrectRopstenForkIdHashesGenerated() { - final Long[] forks = {10L, 1700000L, 4230000L, 4939394L}; - final String genHash = "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"; - final ForkId[] checkIds = { - new ForkId( - Bytes.fromHexString("0x30c7ddbc"), - 10L), // Unsynced, last Frontier, Homestead and first Tangerine block - new ForkId(Bytes.fromHexString("0x63760190"), 1700000L), // First Spurious block - new ForkId(Bytes.fromHexString("0x3ea159c7"), 4230000L), // First Byzantium block - new ForkId(Bytes.fromHexString("0x97b544f3"), 4939394L), // First Constantinople block - new ForkId(Bytes.fromHexString("0xd6e2149b"), 0L) // Today Petersburg block - }; - final List list = Arrays.asList(forks); - final ForkIdManager forkIdManager = new ForkIdManager(mockBlockchain(genHash, 0), list); - final List entries = forkIdManager.getForkAndHashList(); - - assertThat(entries).containsExactly(checkIds); - assertThat(forkIdManager.getLatestForkId()).isNotNull(); - assertThat(forkIdManager.getLatestForkId()).isEqualTo(checkIds[4]); - } - - @Test - public void checkCorrectRinkebyForkIdHashesGenerated() { - final Long[] forks = {1L, 2L, 3L, 1035301L, 3660663L, 4321234L}; - final String genHash = "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177"; - final ForkId[] checkIds = { - new ForkId( - Bytes.fromHexString("0x3b8e0691"), - 1L), // Unsynced, last Frontier, Homestead and first Tangerine block - new ForkId(Bytes.fromHexString("0x60949295"), 2L), // Last Tangerine block - new ForkId(Bytes.fromHexString("0x8bde40dd"), 3L), // First Spurious block - new ForkId(Bytes.fromHexString("0xcb3a64bb"), 1035301L), // First Byzantium block - new ForkId(Bytes.fromHexString("0x8d748b57"), 3660663L), // First Constantinople block - new ForkId(Bytes.fromHexString("0xe49cab14"), 4321234L), // First Petersburg block - new ForkId(Bytes.fromHexString("0xafec6b27"), 0L) // Today Petersburg block - }; - final List list = Arrays.asList(forks); - final ForkIdManager forkIdManager = new ForkIdManager(mockBlockchain(genHash, 0), list); - final List entries = forkIdManager.getForkAndHashList(); - - assertThat(entries).containsExactly(checkIds); - assertThat(forkIdManager.getLatestForkId()).isNotNull(); - assertThat(forkIdManager.getLatestForkId()).isEqualTo(checkIds[6]); - } - - @Test - public void checkCorrectGoerliForkIdHashesGenerated() { - final Long[] forks = {1561651L}; - final String genHash = "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"; - final ForkId[] checkIds = { - new ForkId(Bytes.fromHexString("0xa3f5ab08"), 1561651L), // Frontier->Petersburg - new ForkId(Bytes.fromHexString("0xc25efa5c"), 0L) // Istanbul - }; - final List list = Arrays.asList(forks); - final ForkIdManager forkIdManager = new ForkIdManager(mockBlockchain(genHash, 0), list); - final List entries = forkIdManager.getForkAndHashList(); - - assertThat(entries).containsExactly(checkIds); - assertThat(forkIdManager.getLatestForkId()).isNotNull(); - assertThat(forkIdManager.getLatestForkId()).isEqualTo(checkIds[1]); - } - - @Test - public void check1PetersburgWithRemoteAnnouncingTheSame() { - // 1 Local is mainnet Petersburg, remote announces the same. No future fork is announced. - // {7987396, ID{Hash: 0x668db0af, Next: 0}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7987396L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0x668db0af"), 0L)); - assertThat(result).isTrue(); - assertThat(forkIdManager.getLatestForkId()).isNotNull(); - } - - @Test - public void check2PetersburgWithRemoteAnnouncingTheSameAndNextFork() { - // 2 Local is mainnet Petersburg, remote announces the same. Remote also announces a next fork - // at block 0xffffffff, but that is uncertain. - // {7987396, ID{Hash: 0x668db0af, Next: math.MaxUint64}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7987396L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0x668db0af"), Long.MAX_VALUE)); - assertThat(result).isTrue(); - } - - @Test - public void check3ByzantiumAwareOfPetersburgRemoteUnawareOfPetersburg() { - // 3 Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote - // announces also Byzantium, but it's not yet aware of Petersburg (e.g. non updated node before - // the fork). - // In this case we don't know if Petersburg passed yet or not. - // {7279999, ID{Hash: 0xa00bc324, Next: 0}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7279999L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0xa00bc324"), 0L)); - assertThat(result).isTrue(); - } - - @Test - public void check4ByzantiumAwareOfPetersburgRemoteAwareOfPetersburg() { - // 4 Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote - // announces also Byzantium, and it's also aware of Petersburg (e.g. updated node before the - // fork). We don't know if Petersburg passed yet (will pass) or not. - // {7279999, ID{Hash: 0xa00bc324, Next: 7280000}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7987396L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0xa00bc324"), 7280000L)); - assertThat(result).isTrue(); - } - - @Test - public void check5ByzantiumAwareOfPetersburgRemoteAnnouncingUnknownFork() { - // 5 Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote - // announces also Byzantium, and it's also aware of some random fork (e.g. misconfigured - // Petersburg). - // As neither forks passed at neither nodes, they may mismatch, but we still connect for now. - // {7279999, ID{Hash: 0xa00bc324, Next: math.MaxUint64}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7279999), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0xa00bc324"), Long.MAX_VALUE)); - assertThat(result).isTrue(); - } - - @Test - public void check6PetersburgWithRemoteAnnouncingByzantiumAwareOfPetersburg() { - // 6 Local is mainnet Petersburg, remote announces Byzantium + knowledge about Petersburg. - // Remote is simply out of sync, accept. - // {7987396, ID{Hash: 0x668db0af, Next: 7280000}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7987396L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0x668db0af"), 7280000L)); - assertThat(result).isTrue(); - } - - @Test - public void check7PetersburgWithRemoteAnnouncingSpuriousAwareOfByzantiumRemoteMayNeedUpdate() { - // 7 Local is mainnet Petersburg, remote announces Spurious + knowledge about Byzantium. - // Remote is definitely out of sync. It may or may not need the Petersburg update, we don't know - // yet. - // {7987396, ID{Hash: 0x3edd5b10, Next: 4370000}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7987396L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0x3edd5b10"), 4370000L)); - assertThat(result).isTrue(); - } - - @Test - public void check8ByzantiumWithRemoteAnnouncingPetersburgLocalOutOfSync() { - // 8 Local is mainnet Byzantium, remote announces Petersburg. Local is out of sync, accept. - // {7279999, ID{Hash: 0x668db0af, Next: 0}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 727999L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0x668db0af"), 0L)); - assertThat(result).isTrue(); - } - - @Test - public void check9SpuriousWithRemoteAnnouncingByzantiumRemoteUnawareOfPetersburg() { - // 9 Local is mainnet Spurious, remote announces Byzantium, but is not aware of Petersburg. - // Local out of sync. Local also knows about a future fork, but that is uncertain yet. - // {4369999, ID{Hash: 0xa00bc324, Next: 0}, nil}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 4369999L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0xa00bc324"), 0L)); - assertThat(result).isTrue(); - } - - @Test - public void check10PetersburgWithRemoteAnnouncingByzantiumRemoteUnawareOfAdditionalForks() { - // 10 Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks. - // Remote needs software update. - // {7987396, ID{Hash: 0xa00bc324, Next: 0}, ErrRemoteStale}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7987396L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0xa00bc324"), 0L)); - assertThat(result).isFalse(); - } - - @Test - public void check11PetersburgWithRemoteAnnouncingPetersburgAndFutureForkLocalNeedsUpdate() { - // 11 Local is mainnet Petersburg, and isn't aware of more forks. Remote announces Petersburg + - // 0xffffffff. Local needs software update, reject. - // {7987396, ID{Hash: 0x5cddc0e1, Next: 0}, ErrLocalIncompatibleOrStale}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7987396L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0x5cddc0e1"), 0L)); - assertThat(result).isFalse(); - } - - @Test - public void check12ByzantiumWithRemoteAnnouncingPetersburgAndFutureForkLocalNeedsUpdate() { - // 12 Local is mainnet Byzantium, and is aware of Petersburg. Remote announces Petersburg + - // 0xffffffff. Local needs software update, reject. - // {7279999, ID{Hash: 0x5cddc0e1, Next: 0}, ErrLocalIncompatibleOrStale}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7279999L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0x5cddc0e1"), 0L)); - assertThat(result).isFalse(); - } - - @Test - public void check13ByzantiumWithRemoteAnnouncingRinkebyPetersburg() { - // 13 Local is mainnet Petersburg, remote is Rinkeby Petersburg. - // {7987396, ID{Hash: 0xafec6b27, Next: 0}, ErrLocalIncompatibleOrStale}, - final List forkList = Arrays.asList(forksMainnet); - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(mainnetGenHash, 7987396L), forkList); - final Boolean result = - forkIdManager.peerCheck(new ForkId(Bytes.fromHexString("0xafec6b27"), 0L)); - assertThat(result).isFalse(); - } - - @Test - public void createAndDecodeRLP() { - final ForkId forkIdEntry = new ForkId(Bytes.fromHexString("0xa00bc324"), 7280000L); - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - forkIdEntry.writeTo(out); - final Bytes bytesValue = out.encoded(); - final BytesValueRLPInput in = new BytesValueRLPInput(bytesValue, false); - final ForkId decodedEntry = ForkIdManager.readFrom(in); - assertThat(forkIdEntry).isEqualTo(decodedEntry); - } - - @Test - public void check1ZeroZeroProperRLPEncoding() { - final ForkId forkIdEntry = new ForkId(Bytes.fromHexString("0x00000000"), 0); - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - forkIdEntry.writeTo(out); - final String str1 = "0xc6840000000080"; - final Bytes bytesValue = out.encoded(); - assertThat(str1).isEqualTo(bytesValue.toString()); - final BytesValueRLPInput in = new BytesValueRLPInput(bytesValue, false); - final ForkId decodedEntry = ForkIdManager.readFrom(in); - assertThat(forkIdEntry).isEqualTo(decodedEntry); - } - - @Test - public void check2ArbitraryProperRLPEncoding() { - final ForkId forkIdEntry = new ForkId(Bytes.fromHexString("0xdeadbeef"), 0xbaddcafeL); - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - forkIdEntry.writeTo(out); - final String str1 = "0xca84deadbeef84baddcafe"; - final Bytes bytesValue = out.encoded(); - assertThat(str1).isEqualTo(bytesValue.toString()); - final BytesValueRLPInput in = new BytesValueRLPInput(bytesValue, false); - final ForkId decodedEntry = ForkIdManager.readFrom(in); - assertThat(forkIdEntry).isEqualTo(decodedEntry); - } - - @Test - public void check3MaximumsProperRLPEncoding() { - final ForkId forkIdEntry = new ForkId(Bytes.fromHexString("0xffffffff"), 0xffffffffffffffffL); - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - forkIdEntry.writeTo(out); - final String str1 = - "0xce84ffffffff88ffffffffffffffff"; // Check value supplied in EIP-2124 spec via GO lang - final Bytes bytesValue = out.encoded(); - assertThat(str1).isEqualTo(bytesValue.toString()); - final BytesValueRLPInput in = new BytesValueRLPInput(bytesValue, false); - final ForkId decodedEntry = ForkIdManager.readFrom(in); - assertThat(forkIdEntry).isEqualTo(decodedEntry); - } - - @Test - public void checkConsortiumNetworkAlwaysAcceptPeersIfOnlyZeroForkBlocks() { - final List list = Arrays.asList(0L, 0L, 0L, 0L); - - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(consortiumNetworkGenHash, 0), list); - assertThat(forkIdManager.peerCheck(new ForkId(Bytes.random(32), 0))).isTrue(); - } - - @Test - public void checkAlwaysAcceptPeersIfNull() { - final ForkIdManager forkIdManager = - new ForkIdManager(mockBlockchain(consortiumNetworkGenHash, 0), emptyList()); - assertThat(forkIdManager.peerCheck((ForkId) null)).isTrue(); - } - - @Test - public void assertThatConstructorParametersMustNotBeNull() { - assertThatThrownBy(() -> new ForkIdManager(mockBlockchain(consortiumNetworkGenHash, 0), null)) - .isExactlyInstanceOf(NullPointerException.class); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessageTest.java index 1bdf3dc543..592f356b03 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessageTest.java @@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.eth.EthProtocol; -import org.hyperledger.besu.ethereum.eth.manager.ForkIdManager; +import org.hyperledger.besu.ethereum.eth.manager.ForkId; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import java.math.BigInteger; @@ -75,8 +75,7 @@ public class StatusMessageTest { final Difficulty td = Difficulty.of(1000L); final Hash bestHash = randHash(1L); final Hash genesisHash = randHash(2L); - final ForkIdManager.ForkId forkId = - new ForkIdManager.ForkId(Bytes.fromHexString("0xa00bc334"), 0L); + final ForkId forkId = new ForkId(Bytes.fromHexString("0xa00bc334"), 0L); final MessageData msg = StatusMessage.create(version, networkId, td, bestHash, genesisHash, forkId); diff --git a/util/src/main/java/org/hyperledger/besu/util/EndianUtils.java b/util/src/main/java/org/hyperledger/besu/util/EndianUtils.java new file mode 100644 index 0000000000..baa1cdb512 --- /dev/null +++ b/util/src/main/java/org/hyperledger/besu/util/EndianUtils.java @@ -0,0 +1,34 @@ +/* + * 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.util; + +public class EndianUtils { + // next two methods adopted from: + // https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/util/Pack.java + public static byte[] longToBigEndian(final long n) { + final byte[] bs = new byte[8]; + intToBigEndian((int) (n >>> 32), bs, 0); + intToBigEndian((int) (n & 0xffffffffL), bs, 4); + return bs; + } + + @SuppressWarnings("MethodInputParametersMustBeFinal") + public static void intToBigEndian(final int n, final byte[] bs, int off) { + bs[off] = (byte) (n >>> 24); + bs[++off] = (byte) (n >>> 16); + bs[++off] = (byte) (n >>> 8); + bs[++off] = (byte) (n); + } +}