mirror of https://github.com/hyperledger/besu
[NC-1685] Improve tests for BlockHashOperation (#47)
* Introduce MessageFrameTestFixture to make it easier to test EVM operations. * Add unit tests for BlockHashOperation.
parent
b719a04a5c
commit
3e7a54850b
@ -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; |
||||
} |
||||
} |
@ -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…
Reference in new issue