Fix EIP-2124 Fork identifier for chain compatibility checks. (#1050)

Before this pull request Besu was using the latest known fork id to create status message for Ethereum P2P protocol handshake.
This latest known fork id was created based on a list of forks retrieved from the Genesis.
For private networks it is possible that all fork blocks number are set to 0.
The algorithm to compute the valid fork hashes excludes 0 values.
As a result, the list was empty and the `getLatestForkId` was returning `null`. This is an issue when you support capabilities >= to Eth/64 sub protocol because other peers expect the fork id value in the `RLP` encoded message.
Moreover, the algorithm to compute the fork id should be aware of the chain head number and update `CRC` value only for fork blocks below the current head.
This pull request fixes this issue by fetching the chain head number and update accordingly the `CRC` value.
Unit tests have been extended to cover an exhaustive list of possible combinations on named networks (`goerli`, `rinkeby`, `ropsten` and `mainnet`).

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>
pull/1067/head
Abdelhamid Bakhta 5 years ago committed by GitHub
parent 9f2f3b9fb5
commit 8589177e49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java
  2. 96
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkId.java
  3. 231
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManager.java
  4. 14
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessage.java
  5. 735
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EIP2124Test.java
  6. 389
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ForkIdManagerTest.java
  7. 5
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/StatusMessageTest.java
  8. 34
      util/src/main/java/org/hyperledger/besu/util/EndianUtils.java

@ -23,7 +23,6 @@ import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocol;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; 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.EthPV62;
import org.hyperledger.besu.ethereum.eth.messages.StatusMessage; import org.hyperledger.besu.ethereum.eth.messages.StatusMessage;
import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; 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 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 // TODO: look to consolidate code below if possible
// making status non-final and implementing it above would be one way. // making status non-final and implementing it above would be one way.
final StatusMessage status = final StatusMessage status =

@ -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<ForkId> asList() {
final ArrayList<ForkId> 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();
}
}

@ -15,22 +15,19 @@
package org.hyperledger.besu.ethereum.eth.manager; package org.hyperledger.besu.ethereum.eth.manager;
import static com.google.common.base.Preconditions.checkNotNull; 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.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Hash; 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.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput; import org.hyperledger.besu.util.EndianUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.LongSupplier;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import com.google.common.annotations.VisibleForTesting;
import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.bytes.Bytes32;
@ -39,69 +36,47 @@ public class ForkIdManager {
private final Hash genesisHash; private final Hash genesisHash;
private final List<ForkId> forkAndHashList; private final List<ForkId> forkAndHashList;
private final List<Predicate<ForkId>> forkIDCheckers; private final List<Long> 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<Long> forks) { public ForkIdManager(final Blockchain blockchain, final List<Long> nonFilteredForks) {
checkNotNull(blockchain); checkNotNull(blockchain);
checkNotNull(forks); checkNotNull(nonFilteredForks);
this.chainHeadSupplier = blockchain::getChainHeadBlockNumber;
this.genesisHash = blockchain.getGenesisBlock().getHash(); this.genesisHash = blockchain.getGenesisBlock().getHash();
this.forkAndHashList = new ArrayList<>(); this.forkAndHashList = new ArrayList<>();
final Predicate<ForkId> legacyForkIdChecker = this.forks =
createForkIDChecker( nonFilteredForks.stream()
blockchain,
genesisHash,
forks,
fs ->
fs.stream()
.filter(fork -> fork > 0) .filter(fork -> fork > 0)
.distinct() .distinct()
.collect(Collectors.toUnmodifiableList()), .sorted()
forkAndHashList); .collect(Collectors.toUnmodifiableList());
// if the fork list contains only zeros then we may be in a consortium/dev network this.onlyZerosForkBlocks = nonFilteredForks.stream().allMatch(value -> 0L == value);
if (onlyZerosForkBlocks(forks)) { this.forkNext = createForkIds();
this.forkIDCheckers = singletonList(forkId -> true); this.highestKnownFork = !forks.isEmpty() ? forks.get(forks.size() - 1) : 0L;
} else {
final Predicate<ForkId> newForkIdChecker =
createForkIDChecker(
blockchain,
genesisHash,
forks,
fs -> fs.stream().distinct().collect(Collectors.toUnmodifiableList()),
new ArrayList<>());
this.forkIDCheckers = Arrays.asList(newForkIdChecker, legacyForkIdChecker);
}
} }
private static Predicate<ForkId> createForkIDChecker( public ForkId computeForkId() {
final Blockchain blockchain, final long head = chainHeadSupplier.getAsLong();
final Hash genesisHash, for (final ForkId forkId : forkAndHashList) {
final List<Long> forks, if (head < forkId.getNext()) {
final Function<List<Long>, List<Long>> sanitizer, return forkId;
final List<ForkId> forkIds) {
final List<Long> sanitizedForks = sanitizer.apply(forks);
final long forkNext = createForkIds(genesisHash, sanitizedForks, forkIds);
return eip2124(blockchain, forkNext, forkIds, highestKnownFork(sanitizedForks));
} }
private static boolean onlyZerosForkBlocks(final List<Long> forks) {
return forks.stream().allMatch(value -> 0L == value);
} }
return forkAndHashList.isEmpty()
private static long highestKnownFork(final List<Long> forks) { ? new ForkId(genesisHashCrc, 0)
return !forks.isEmpty() ? forks.get(forks.size() - 1) : 0L; : forkAndHashList.get(forkAndHashList.size() - 1);
} }
public List<ForkId> getForkAndHashList() { @VisibleForTesting
List<ForkId> getForkAndHashList() {
return this.forkAndHashList; return this.forkAndHashList;
} }
ForkId getLatestForkId() {
if (forkAndHashList.size() > 0) {
return forkAndHashList.get(forkAndHashList.size() - 1);
}
return null;
}
public static ForkId readFrom(final RLPInput in) { public static ForkId readFrom(final RLPInput in) {
in.enterList(); in.enterList();
final Bytes hash = in.readBytes(); final Bytes hash = in.readBytes();
@ -117,16 +92,7 @@ public class ForkIdManager {
* @return boolean (peer valid (true) or invalid (false)) * @return boolean (peer valid (true) or invalid (false))
*/ */
boolean peerCheck(final ForkId forkId) { boolean peerCheck(final ForkId forkId) {
return forkIDCheckers.stream().anyMatch(checker -> checker.test(forkId)); if (forkId == null || onlyZerosForkBlocks) {
}
private static Predicate<ForkId> eip2124(
final Blockchain blockchain,
final long forkNext,
final List<ForkId> forkAndHashList,
final long highestKnownFork) {
return forkId -> {
if (forkId == null) {
return true; // Another method must be used to validate (i.e. genesis hash) return true; // Another method must be used to validate (i.e. genesis hash)
} }
// Run the fork checksum validation rule set: // Run the fork checksum validation rule set:
@ -145,22 +111,14 @@ public class ForkIdManager {
// the remote, but at this current point in time we don't have enough // the remote, but at this current point in time we don't have enough
// information. // information.
// 4. Reject in all other cases. // 4. Reject in all other cases.
if (isHashKnown(forkId.getHash(), forkAndHashList)) { if (!isHashKnown(forkId.getHash())) {
if (blockchain.getChainHeadBlockNumber() < forkNext) {
return true;
} else {
if (isForkKnown(forkId.getNext(), highestKnownFork, forkAndHashList)) {
return isRemoteAwareOfPresent(
forkId.getHash(), forkId.getNext(), highestKnownFork, forkAndHashList);
} else {
return false; return false;
} }
return chainHeadSupplier.getAsLong() < forkNext
|| (isForkKnown(forkId.getNext())
&& isRemoteAwareOfPresent(forkId.getHash(), forkId.getNext()));
} }
} else {
return false;
}
};
}
/** /**
* Non EIP-2124 behaviour * Non EIP-2124 behaviour
* *
@ -171,21 +129,16 @@ public class ForkIdManager {
return !peerGenesisHash.equals(genesisHash); return !peerGenesisHash.equals(genesisHash);
} }
private static boolean isHashKnown(final Bytes forkHash, final List<ForkId> forkAndHashList) { private boolean isHashKnown(final Bytes forkHash) {
return forkAndHashList.stream().map(ForkId::getHash).anyMatch(hash -> hash.equals(forkHash)); return forkAndHashList.stream().map(ForkId::getHash).anyMatch(hash -> hash.equals(forkHash));
} }
private static boolean isForkKnown( private boolean isForkKnown(final Long nextFork) {
final Long nextFork, final long highestKnownFork, final List<ForkId> forkAndHashList) {
return highestKnownFork < nextFork return highestKnownFork < nextFork
|| forkAndHashList.stream().map(ForkId::getNext).anyMatch(fork -> fork.equals(nextFork)); || forkAndHashList.stream().map(ForkId::getNext).anyMatch(fork -> fork.equals(nextFork));
} }
private static boolean isRemoteAwareOfPresent( private boolean isRemoteAwareOfPresent(final Bytes forkHash, final Long nextFork) {
final Bytes forkHash,
final Long nextFork,
final long highestKnownFork,
final List<ForkId> forkAndHashList) {
for (final ForkId j : forkAndHashList) { for (final ForkId j : forkAndHashList) {
if (forkHash.equals(j.getHash())) { if (forkHash.equals(j.getHash())) {
if (nextFork.equals(j.getNext())) { if (nextFork.equals(j.getNext())) {
@ -200,121 +153,35 @@ public class ForkIdManager {
return false; return false;
} }
private static long createForkIds( private long createForkIds() {
final Hash genesisHash, final List<Long> forks, final List<ForkId> forkIds) {
final CRC32 crc = new CRC32(); final CRC32 crc = new CRC32();
crc.update(genesisHash.toArray()); crc.update(genesisHash.toArray());
final List<Bytes> forkHashes = new ArrayList<>(List.of(getCurrentCrcHash(crc))); genesisHashCrc = getCurrentCrcHash(crc);
for (final Long fork : forks) { final List<Bytes> forkHashes = new ArrayList<>(List.of(genesisHashCrc));
forks.forEach(
fork -> {
updateCrc(crc, fork); updateCrc(crc, fork);
forkHashes.add(getCurrentCrcHash(crc)); forkHashes.add(getCurrentCrcHash(crc));
} });
// This loop is for all the fork hashes that have an associated "next fork" // This loop is for all the fork hashes that have an associated "next fork"
for (int i = 0; i < forks.size(); i++) { 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; long forkNext = 0;
if (!forks.isEmpty()) { if (!forks.isEmpty()) {
forkNext = forkIds.get(forkIds.size() - 1).getNext(); forkNext = forkAndHashList.get(forkAndHashList.size() - 1).getNext();
forkIds.add(new ForkId(forkHashes.get(forkHashes.size() - 1), 0)); forkAndHashList.add(new ForkId(forkHashes.get(forkHashes.size() - 1), 0));
} }
return forkNext; return forkNext;
} }
private static void updateCrc(final CRC32 crc, final Long block) { 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); crc.update(byteRepresentationFork, 0, byteRepresentationFork.length);
} }
private static Bytes getCurrentCrcHash(final CRC32 crc) { private static Bytes getCurrentCrcHash(final CRC32 crc) {
return Bytes.ofUnsignedInt(crc.getValue()); 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<ForkId> asList() {
final ArrayList<ForkId> 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);
}
} }

@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.eth.messages;
import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Hash; 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.AbstractMessageData;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
@ -57,7 +57,7 @@ public final class StatusMessage extends AbstractMessageData {
final Difficulty totalDifficulty, final Difficulty totalDifficulty,
final Hash bestHash, final Hash bestHash,
final Hash genesisHash, final Hash genesisHash,
final ForkIdManager.ForkId forkId) { final ForkId forkId) {
final EthStatus status = final EthStatus status =
new EthStatus(protocolVersion, networkId, totalDifficulty, bestHash, genesisHash, forkId); new EthStatus(protocolVersion, networkId, totalDifficulty, bestHash, genesisHash, forkId);
final BytesValueRLPOutput out = new BytesValueRLPOutput(); 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. */ /** @return The fork id of the network the associated node is participating in. */
public ForkIdManager.ForkId forkId() { public ForkId forkId() {
return status().forkId; return status().forkId;
} }
@ -129,7 +129,7 @@ public final class StatusMessage extends AbstractMessageData {
private final Difficulty totalDifficulty; private final Difficulty totalDifficulty;
private final Hash bestHash; private final Hash bestHash;
private final Hash genesisHash; private final Hash genesisHash;
private final ForkIdManager.ForkId forkId; private final ForkId forkId;
EthStatus( EthStatus(
final int protocolVersion, final int protocolVersion,
@ -151,7 +151,7 @@ public final class StatusMessage extends AbstractMessageData {
final Difficulty totalDifficulty, final Difficulty totalDifficulty,
final Hash bestHash, final Hash bestHash,
final Hash genesisHash, final Hash genesisHash,
final ForkIdManager.ForkId forkHash) { final ForkId forkHash) {
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
this.networkId = networkId; this.networkId = networkId;
this.totalDifficulty = totalDifficulty; this.totalDifficulty = totalDifficulty;
@ -182,9 +182,9 @@ public final class StatusMessage extends AbstractMessageData {
final Difficulty totalDifficulty = Difficulty.of(in.readUInt256Scalar()); final Difficulty totalDifficulty = Difficulty.of(in.readUInt256Scalar());
final Hash bestHash = Hash.wrap(in.readBytes32()); final Hash bestHash = Hash.wrap(in.readBytes32());
final Hash genesisHash = Hash.wrap(in.readBytes32()); final Hash genesisHash = Hash.wrap(in.readBytes32());
final ForkIdManager.ForkId forkId; final ForkId forkId;
if (in.nextIsList()) { if (in.nextIsList()) {
forkId = ForkIdManager.ForkId.readFrom(in); forkId = ForkId.readFrom(in);
} else { } else {
forkId = null; forkId = null;
} }

@ -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<Object[]> 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<ForkId> wantForkId;
private final Optional<List<ForkId>> wantForkIds;
private final Optional<PeerCheckCase> 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<ForkId> wantForkId,
final Optional<List<ForkId>> wantForkIds,
final Optional<PeerCheckCase> 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<Long> MAINNET =
Arrays.asList(
1920000L, 1150000L, 2463000L, 2675000L, 2675000L, 4370000L, 7280000L, 7280000L,
9069000L, 9200000L);
private static final List<Long> ROPSTEN =
Arrays.asList(0L, 0L, 10L, 1700000L, 4230000L, 4939394L, 6485846L, 7117117L);
private static final List<Long> RINKEBY =
Arrays.asList(1L, 2L, 3L, 3L, 1035301L, 3660663L, 4321234L, 5435345L);
private static final List<Long> GOERLI = Arrays.asList(0L, 0L, 0L, 0L, 0L, 0L, 0L, 1561651L);
private static final List<Long> PRIVATE = Arrays.asList(0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L);
}
private static class ForkIds {
private static final List<ForkId> 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<ForkId> 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<ForkId> 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<ForkId> 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<Long> forks;
Network(final String hash, final List<Long> forks) {
this.hash = hash;
this.forks = forks;
}
private static Network network(final String hash, final List<Long> 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<ForkId> wantForkId(final String hash, final long next) {
return Optional.of(forkId(hash, next));
}
private static Optional<PeerCheckCase> wantPeerCheck(
final String hash, final long next, final boolean want) {
return Optional.of(new PeerCheckCase(hash, next, want));
}
}

@ -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<Long> list = Arrays.asList(forksMainnet);
final ForkIdManager forkIdManager = new ForkIdManager(mockBlockchain(mainnetGenHash, 0), list);
final List<ForkId> 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<Long> list = Arrays.asList(forks);
final ForkIdManager forkIdManager = new ForkIdManager(mockBlockchain(genHash, 0), list);
final List<ForkId> 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<Long> list = Arrays.asList(forks);
final ForkIdManager forkIdManager = new ForkIdManager(mockBlockchain(genHash, 0), list);
final List<ForkId> 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<Long> list = Arrays.asList(forks);
final ForkIdManager forkIdManager = new ForkIdManager(mockBlockchain(genHash, 0), list);
final List<ForkId> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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);
}
}

@ -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.Difficulty;
import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.eth.EthProtocol; 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 org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
import java.math.BigInteger; import java.math.BigInteger;
@ -75,8 +75,7 @@ public class StatusMessageTest {
final Difficulty td = Difficulty.of(1000L); final Difficulty td = Difficulty.of(1000L);
final Hash bestHash = randHash(1L); final Hash bestHash = randHash(1L);
final Hash genesisHash = randHash(2L); final Hash genesisHash = randHash(2L);
final ForkIdManager.ForkId forkId = final ForkId forkId = new ForkId(Bytes.fromHexString("0xa00bc334"), 0L);
new ForkIdManager.ForkId(Bytes.fromHexString("0xa00bc334"), 0L);
final MessageData msg = final MessageData msg =
StatusMessage.create(version, networkId, td, bestHash, genesisHash, forkId); StatusMessage.create(version, networkId, td, bestHash, genesisHash, forkId);

@ -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);
}
}
Loading…
Cancel
Save