Skip to content

Commit

Permalink
[NC-1685] Improve tests for BlockHashOperation (PegaSysEng#47)
Browse files Browse the repository at this point in the history
* Introduce MessageFrameTestFixture to make it easier to test EVM operations.

* Add unit tests for BlockHashOperation.
  • Loading branch information
ajsutton authored Oct 12, 2018
1 parent b719a04 commit 3e7a548
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ default boolean isSuccessful() {
* @param worldState The current world state
* @param blockHeader The current block header
* @param transaction The transaction to process
* @param miningBeneficiary the address which is to receive the transaction fee
* @param miningBeneficiary The address which is to receive the transaction fee
* @param blockHashLookup The {@link BlockHashLookup} to use for BLOCKHASH operations
* @return the transaction result
*/
default Result processTransaction(
Expand Down Expand Up @@ -120,7 +121,8 @@ default Result processTransaction(
* @param blockHeader The current block header
* @param transaction The transaction to process
* @param operationTracer The tracer to record results of each EVM operation
* @param miningBeneficiary the address which is to receive the transaction fee
* @param miningBeneficiary The address which is to receive the transaction fee
* @param blockHashLookup The {@link BlockHashLookup} to use for BLOCKHASH operations
* @return the transaction result
*/
Result processTransaction(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package net.consensys.pantheon.ethereum.core;

import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.AddressHelpers;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.ExecutionContextTestFixture;
import tech.pegasys.pantheon.ethereum.core.Gas;
import tech.pegasys.pantheon.ethereum.core.MutableWorldState;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.core.WorldUpdater;
import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup;
import tech.pegasys.pantheon.ethereum.vm.Code;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame.Type;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Optional;

public class MessageFrameTestFixture {

private static final Address DEFAUT_ADDRESS = AddressHelpers.ofValue(244259721);

private Type type = Type.MESSAGE_CALL;
private Deque<MessageFrame> messageFrameStack = new ArrayDeque<>();
private Optional<Blockchain> blockchain = Optional.empty();
private Optional<WorldUpdater> worldState = Optional.empty();
private Gas initialGas = Gas.MAX_VALUE;
private Address address = DEFAUT_ADDRESS;
private Address sender = DEFAUT_ADDRESS;
private Address originator = DEFAUT_ADDRESS;
private Address contract = DEFAUT_ADDRESS;
private Wei gasPrice = Wei.ZERO;
private Wei value = Wei.ZERO;
private BytesValue inputData = BytesValue.EMPTY;
private Code code = new Code(BytesValue.EMPTY);
private final List<Bytes32> stackItems = new ArrayList<>();
private Optional<BlockHeader> blockHeader = Optional.empty();
private int depth = 0;
private Optional<BlockHashLookup> blockHashLookup = Optional.empty();
private ExecutionContextTestFixture executionContextTestFixture;

public MessageFrameTestFixture type(final Type type) {
this.type = type;
return this;
}

public MessageFrameTestFixture messageFrameStack(final Deque<MessageFrame> messageFrameStack) {
this.messageFrameStack = messageFrameStack;
return this;
}

public MessageFrameTestFixture blockchain(final Blockchain blockchain) {
this.blockchain = Optional.of(blockchain);
return this;
}

public MessageFrameTestFixture worldState(final WorldUpdater worldState) {
this.worldState = Optional.of(worldState);
return this;
}

public MessageFrameTestFixture worldState(final MutableWorldState worldState) {
this.worldState = Optional.of(worldState.updater());
return this;
}

public MessageFrameTestFixture initialGas(final Gas initialGas) {
this.initialGas = initialGas;
return this;
}

public MessageFrameTestFixture sender(final Address sender) {
this.sender = sender;
return this;
}

public MessageFrameTestFixture address(final Address address) {
this.address = address;
return this;
}

public MessageFrameTestFixture originator(final Address originator) {
this.originator = originator;
return this;
}

public MessageFrameTestFixture contract(final Address contract) {
this.contract = contract;
return this;
}

public MessageFrameTestFixture gasPrice(final Wei gasPrice) {
this.gasPrice = gasPrice;
return this;
}

public MessageFrameTestFixture value(final Wei value) {
this.value = value;
return this;
}

public MessageFrameTestFixture inputData(final BytesValue inputData) {
this.inputData = inputData;
return this;
}

public MessageFrameTestFixture code(final Code code) {
this.code = code;
return this;
}

public MessageFrameTestFixture blockHeader(final BlockHeader blockHeader) {
this.blockHeader = Optional.of(blockHeader);
return this;
}

public MessageFrameTestFixture depth(final int depth) {
this.depth = depth;
return this;
}

public MessageFrameTestFixture pushStackItem(final Bytes32 item) {
stackItems.add(item);
return this;
}

public MessageFrameTestFixture blockHashLookup(final BlockHashLookup blockHashLookup) {
this.blockHashLookup = Optional.of(blockHashLookup);
return this;
}

public MessageFrame build() {
final Blockchain blockchain = this.blockchain.orElseGet(this::createDefaultBlockchain);
final BlockHeader blockHeader =
this.blockHeader.orElseGet(() -> blockchain.getBlockHeader(0).get());
final MessageFrame frame =
MessageFrame.builder()
.type(type)
.messageFrameStack(messageFrameStack)
.blockchain(blockchain)
.worldState(worldState.orElseGet(this::createDefaultWorldState))
.initialGas(initialGas)
.address(address)
.originator(originator)
.gasPrice(gasPrice)
.inputData(inputData)
.sender(sender)
.value(value)
.apparentValue(value)
.contract(contract)
.code(code)
.blockHeader(blockHeader)
.depth(depth)
.completer(c -> {})
.miningBeneficiary(blockHeader.getCoinbase())
.blockHashLookup(
blockHashLookup.orElseGet(() -> new BlockHashLookup(blockHeader, blockchain)))
.build();
stackItems.forEach(frame::pushStackItem);
return frame;
}

private WorldUpdater createDefaultWorldState() {
return getOrCreateExecutionContextTestFixture().getStateArchive().getMutable().updater();
}

private Blockchain createDefaultBlockchain() {
return getOrCreateExecutionContextTestFixture().getBlockchain();
}

private ExecutionContextTestFixture getOrCreateExecutionContextTestFixture() {
// Avoid creating a test fixture if the test supplies the blockchain and worldstate.
if (executionContextTestFixture == null) {
executionContextTestFixture = new ExecutionContextTestFixture();
}
return executionContextTestFixture;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
import tech.pegasys.pantheon.ethereum.mainnet.PrecompileContractRegistry;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec;
import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup;
import tech.pegasys.pantheon.ethereum.vm.Code;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame.Type;
import tech.pegasys.pantheon.ethereum.vm.OperationTracer;
import tech.pegasys.pantheon.util.bytes.BytesValue;

Expand All @@ -18,6 +16,8 @@
import java.util.Deque;
import java.util.function.Consumer;

import net.consensys.pantheon.ethereum.core.MessageFrameTestFixture;

public class TestCodeExecutor {

private final ExecutionContextTestFixture fixture;
Expand Down Expand Up @@ -50,8 +50,7 @@ public MessageFrame executeCode(
.nonce(0)
.build();
final MessageFrame initialFrame =
MessageFrame.builder()
.type(Type.MESSAGE_CALL)
new MessageFrameTestFixture()
.messageFrameStack(messageFrameStack)
.blockchain(fixture.getBlockchain())
.worldState(worldState)
Expand All @@ -63,13 +62,9 @@ public MessageFrame executeCode(
.inputData(transaction.getPayload())
.sender(SENDER_ADDRESS)
.value(transaction.getValue())
.apparentValue(transaction.getValue())
.code(new Code(BytesValue.fromHexString(code)))
.blockHeader(blockHeader)
.depth(0)
.completer(c -> {})
.miningBeneficiary(blockHeader.coinbase)
.blockHashLookup(new BlockHashLookup(blockHeader, fixture.getBlockchain()))
.build();
messageFrameStack.addFirst(initialFrame);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package net.consensys.pantheon.ethereum.vm.operations;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.mainnet.FrontierGasCalculator;
import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.ethereum.vm.operations.BlockHashOperation;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;

import com.google.common.base.Strings;
import net.consensys.pantheon.ethereum.core.MessageFrameTestFixture;
import org.junit.After;
import org.junit.Test;

public class BlockHashOperationTest {

private static final int MAXIMUM_COMPLETE_BLOCKS_BEHIND = 256;
private final BlockHashLookup blockHashLookup = mock(BlockHashLookup.class);
private final BlockHashOperation blockHashOperation =
new BlockHashOperation(new FrontierGasCalculator());

@After
public void verifyNoUnexpectedHashLookups() {
verifyNoMoreInteractions(blockHashLookup);
}

@Test
public void shouldReturnZeroWhenArgIsBiggerThanALong() {
assertBlockHash(Bytes32.fromHexString(Strings.repeat("F", 64)), Bytes32.ZERO, 100);
}

@Test
public void shouldReturnZeroWhenCurrentBlockIsGenesis() {
assertBlockHash(Bytes32.ZERO, Bytes32.ZERO, BlockHeader.GENESIS_BLOCK_NUMBER);
}

@Test
public void shouldReturnZeroWhenRequestedBlockAheadOfCurrent() {
assertBlockHash(250, Bytes32.ZERO, 100);
}

@Test
public void shouldReturnZeroWhenRequestedBlockTooFarBehindCurrent() {
final int requestedBlock = 10;
// Our block is the one after the chain head (it's a new block), hence the + 1.
final int importingBlockNumber = MAXIMUM_COMPLETE_BLOCKS_BEHIND + requestedBlock + 1;
assertBlockHash(requestedBlock, Bytes32.ZERO, importingBlockNumber);
}

@Test
public void shouldReturnZeroWhenRequestedBlockGreaterThanImportingBlock() {
assertBlockHash(101, Bytes32.ZERO, 100);
}

@Test
public void shouldReturnZeroWhenRequestedBlockEqualToImportingBlock() {
assertBlockHash(100, Bytes32.ZERO, 100);
}

@Test
public void shouldReturnBlockHashUsingLookupFromFrameWhenItIsWithinTheAllowedRange() {
final Hash blockHash = Hash.hash(BytesValue.fromHexString("0x1293487297"));
when(blockHashLookup.getBlockHash(100)).thenReturn(blockHash);
assertBlockHash(100, blockHash, 200);
verify(blockHashLookup).getBlockHash(100);
}

private void assertBlockHash(
final long requestedBlock, final Bytes32 expectedOutput, final long currentBlockNumber) {
assertBlockHash(UInt256.of(requestedBlock).getBytes(), expectedOutput, currentBlockNumber);
}

private void assertBlockHash(
final Bytes32 input, final Bytes32 expectedOutput, final long currentBlockNumber) {
final MessageFrame frame =
new MessageFrameTestFixture()
.blockHashLookup(blockHashLookup)
.blockHeader(new BlockHeaderTestFixture().number(currentBlockNumber).buildHeader())
.pushStackItem(input)
.build();
blockHashOperation.execute(frame);
final Bytes32 result = frame.popStackItem();
assertThat(result).isEqualTo(expectedOutput);
assertThat(frame.stackSize()).isZero();
}
}
Loading

0 comments on commit 3e7a548

Please sign in to comment.