diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/BlockHeaderBuilder.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/BlockHeaderBuilder.java index d33ba9ddd4..c114e7f462 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/BlockHeaderBuilder.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/BlockHeaderBuilder.java @@ -63,6 +63,25 @@ public class BlockHeaderBuilder { return new BlockHeaderBuilder(); } + public static BlockHeaderBuilder fromHeader(final BlockHeader header) { + return create() + .parentHash(header.getParentHash()) + .ommersHash(header.getOmmersHash()) + .coinbase(header.getCoinbase()) + .stateRoot(header.getStateRoot()) + .transactionsRoot(header.getTransactionsRoot()) + .receiptsRoot(header.getReceiptsRoot()) + .logsBloom(header.getLogsBloom()) + .difficulty(header.getDifficulty()) + .number(header.getNumber()) + .gasLimit(header.getGasLimit()) + .gasUsed(header.getGasUsed()) + .timestamp(header.getTimestamp()) + .extraData(header.getExtraData()) + .mixHash(header.getMixHash()) + .nonce(header.getNonce()); + } + public BlockHeader buildBlockHeader() { validateBlockHeader(); diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRule.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRule.java index 0ff92e5866..5197b582da 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRule.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRule.java @@ -12,64 +12,34 @@ */ package tech.pegasys.pantheon.ethereum.mainnet.headervalidationrules; -import tech.pegasys.pantheon.crypto.BouncyCastleMessageDigestFactory; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.mainnet.DetachedBlockHeaderValidationRule; import tech.pegasys.pantheon.ethereum.mainnet.EthHasher; -import tech.pegasys.pantheon.ethereum.rlp.RLP; -import tech.pegasys.pantheon.ethereum.rlp.RlpUtils; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; import tech.pegasys.pantheon.util.bytes.Bytes32; import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValues; import tech.pegasys.pantheon.util.uint.UInt256; import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public final class ProofOfWorkValidationRule implements DetachedBlockHeaderValidationRule { - private static final Logger LOG = LogManager.getLogger(ProofOfWorkValidationRule.class); - - private static final int SERIALIZED_HASH_SIZE = 33; - - private static final int SERIALIZED_NONCE_SIZE = 9; + private static final Logger LOG = LogManager.getLogger(); private static final BigInteger ETHHASH_TARGET_UPPER_BOUND = BigInteger.valueOf(2).pow(256); private static final EthHasher HASHER = new EthHasher.Light(); - private static final ThreadLocal KECCAK_256 = - ThreadLocal.withInitial( - () -> { - try { - return BouncyCastleMessageDigestFactory.create( - tech.pegasys.pantheon.crypto.Hash.KECCAK256_ALG); - } catch (final NoSuchAlgorithmException ex) { - throw new IllegalStateException(ex); - } - }); - @Override public boolean validate(final BlockHeader header, final BlockHeader parent) { - final MessageDigest keccak256 = KECCAK_256.get(); - - final byte[] bytes = RLP.encode(header::writeTo).extractArray(); - final int listOffset = RlpUtils.decodeOffset(bytes, 0); - final int length = RlpUtils.decodeLength(bytes, 0); - - final byte[] listHeadBuff = new byte[10]; - final int newLength = length - SERIALIZED_HASH_SIZE - SERIALIZED_NONCE_SIZE; - final int sizeLen = writeListPrefix(newLength - listOffset, listHeadBuff); - - keccak256.update(listHeadBuff, 0, sizeLen); - keccak256.update(bytes, listOffset, newLength - sizeLen); final byte[] hashBuffer = new byte[64]; - HASHER.hash(hashBuffer, header.getNonce(), header.getNumber(), keccak256.digest()); + final Hash headerHash = hashHeader(header); + HASHER.hash(hashBuffer, header.getNonce(), header.getNumber(), headerHash.extractArray()); if (header.getDifficulty().isZero()) { LOG.trace("Rejecting header because difficulty is 0"); @@ -105,19 +75,31 @@ public final class ProofOfWorkValidationRule implements DetachedBlockHeaderValid return true; } + private Hash hashHeader(final BlockHeader header) { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + + // Encode header without nonce and mixhash + out.startList(); + out.writeBytesValue(header.getParentHash()); + out.writeBytesValue(header.getOmmersHash()); + out.writeBytesValue(header.getCoinbase()); + out.writeBytesValue(header.getStateRoot()); + out.writeBytesValue(header.getTransactionsRoot()); + out.writeBytesValue(header.getReceiptsRoot()); + out.writeBytesValue(header.getLogsBloom().getBytes()); + out.writeUInt256Scalar(header.getDifficulty()); + out.writeLongScalar(header.getNumber()); + out.writeLongScalar(header.getGasLimit()); + out.writeLongScalar(header.getGasUsed()); + out.writeLongScalar(header.getTimestamp()); + out.writeBytesValue(header.getExtraData()); + out.endList(); + + return Hash.hash(out.encoded()); + } + @Override public boolean includeInLightValidation() { return false; } - - private static int writeListPrefix(final int size, final byte[] target) { - final int sizeLength = 4 - Integer.numberOfLeadingZeros(size) / 8; - target[0] = (byte) (0xf7 + sizeLength); - int shift = 0; - for (int i = 0; i < sizeLength; i++) { - target[sizeLength - i] = (byte) (size >> shift); - shift += 8; - } - return 1 + sizeLength; - } } diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRuleTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRuleTest.java new file mode 100644 index 0000000000..88daee1dc5 --- /dev/null +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRuleTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2018 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. + */ +package tech.pegasys.pantheon.ethereum.mainnet.headervalidationrules; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.ethereum.core.BlockHashFunction; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.ethereum.mainnet.ScheduleBasedBlockHashFunction; +import tech.pegasys.pantheon.ethereum.mainnet.ValidationTestUtils; +import tech.pegasys.pantheon.util.uint.UInt256; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collection; + +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 ProofOfWorkValidationRuleTest { + + private final BlockHeader blockHeader; + private final BlockHeader parentHeader; + private final ProofOfWorkValidationRule validationRule; + + public ProofOfWorkValidationRuleTest(final long parentBlockNum, final long blockNum) + throws IOException { + blockHeader = ValidationTestUtils.readHeader(parentBlockNum); + parentHeader = ValidationTestUtils.readHeader(blockNum); + validationRule = new ProofOfWorkValidationRule(); + } + + @Parameters(name = "block {1}") + public static Collection data() { + + return Arrays.asList( + new Object[][] { + {300005, 300006}, + {1200000, 1200001}, + {4400000, 4400001}, + {4400001, 4400002} + }); + } + + @Test + public void validatesValidBlocks() { + assertThat(validationRule.validate(blockHeader, parentHeader)).isTrue(); + } + + @Test + public void failsBlockWithZeroValuedDifficulty() { + BlockHeader header = + BlockHeaderBuilder.fromHeader(blockHeader) + .difficulty(UInt256.ZERO) + .blockHashFunction(mainnetBlockHashFunction()) + .buildBlockHeader(); + assertThat(validationRule.validate(header, parentHeader)).isFalse(); + } + + @Test + public void failsWithVeryLargeDifficulty() { + UInt256 largeDifficulty = UInt256.of(BigInteger.valueOf(2).pow(255)); + BlockHeader header = + BlockHeaderBuilder.fromHeader(blockHeader) + .difficulty(largeDifficulty) + .blockHashFunction(mainnetBlockHashFunction()) + .buildBlockHeader(); + assertThat(validationRule.validate(header, parentHeader)).isFalse(); + } + + @Test + public void failsWithMisMatchedMixHash() { + Hash updateMixHash = Hash.wrap(blockHeader.getMixHash().asUInt256().minus(1L).getBytes()); + BlockHeader header = + BlockHeaderBuilder.fromHeader(blockHeader) + .mixHash(updateMixHash) + .blockHashFunction(mainnetBlockHashFunction()) + .buildBlockHeader(); + assertThat(validationRule.validate(header, parentHeader)).isFalse(); + } + + @Test + public void failsWithMisMatchedNonce() { + long updatedNonce = blockHeader.getNonce() + 1; + BlockHeader header = + BlockHeaderBuilder.fromHeader(blockHeader) + .nonce(updatedNonce) + .blockHashFunction(mainnetBlockHashFunction()) + .buildBlockHeader(); + assertThat(validationRule.validate(header, parentHeader)).isFalse(); + } + + private BlockHashFunction mainnetBlockHashFunction() { + ProtocolSchedule protocolSchedule = MainnetProtocolSchedule.create(); + return ScheduleBasedBlockHashFunction.create(protocolSchedule); + } +}