[NC-1685][NC-1644] BlockHashOperation fixes (#38)

* [NC-1685] Fix BlockHashOperation so it handles blocks being added on forks and gets the hash of the block at the requested number on that fork rather than on the current canonical chain.  

 * [NC-1644] Cache block hashes across all transactions in a block.
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Adrian Sutton 6 years ago committed by GitHub
parent feee715f6f
commit 84d89fe56e
  1. 11
      ethereum/core/src/integration-test/java/net/consensys/pantheon/ethereum/vm/TraceTransactionIntegrationTest.java
  2. 9
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/blockcreation/BlockTransactionSelector.java
  3. 17
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/mainnet/MainnetBlockProcessor.java
  4. 6
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/mainnet/MainnetTransactionProcessor.java
  5. 15
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/mainnet/TransactionProcessor.java
  6. 1
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/vm/AbstractCallOperation.java
  7. 44
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/vm/BlockHashLookup.java
  8. 19
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/vm/MessageFrame.java
  9. 1
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/vm/operations/AbstractCreateOperation.java
  10. 10
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/vm/operations/BlockHashOperation.java
  11. 2
      ethereum/core/src/test-support/java/net/consensys/pantheon/ethereum/core/TestCodeExecutor.java
  12. 21
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java
  13. 3
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/core/BlockHeaderMock.java
  14. 107
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/vm/BlockHashLookupTest.java
  15. 10
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/vm/DebugOperationTracerTest.java
  16. 6
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/vm/GeneralStateReferenceTestTools.java
  17. 29
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/vm/TestBlockchain.java
  18. 4
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/vm/VMReferenceTest.java
  19. 6
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/vm/operations/ExtCodeHashOperationTest.java
  20. 8
      ethereum/jsonrpc/src/main/java/net/consensys/pantheon/ethereum/jsonrpc/internal/processor/BlockReplay.java
  21. 4
      ethereum/jsonrpc/src/main/java/net/consensys/pantheon/ethereum/jsonrpc/internal/processor/TransactionTracer.java
  22. 4
      ethereum/jsonrpc/src/main/java/net/consensys/pantheon/ethereum/jsonrpc/internal/processor/TransientTransactionProcessor.java
  23. 5
      ethereum/jsonrpc/src/test/java/net/consensys/pantheon/ethereum/jsonrpc/internal/methods/TransientTransactionProcessorTest.java
  24. 19
      ethereum/jsonrpc/src/test/java/net/consensys/pantheon/ethereum/jsonrpc/internal/processor/TransactionTracerTest.java

@ -50,6 +50,7 @@ public class TraceTransactionIntegrationTest {
private WorldStateArchive worldStateArchive;
private Block genesisBlock;
private TransactionProcessor transactionProcessor;
private BlockHashLookup blockHashLookup;
@Before
public void setUp() {
@ -64,6 +65,7 @@ public class TraceTransactionIntegrationTest {
final ProtocolSchedule<Void> protocolSchedule = genesisConfig.getProtocolSchedule();
genesisConfig.writeStateTo(worldStateArchive.getMutable(Hash.EMPTY_TRIE_HASH));
transactionProcessor = protocolSchedule.getByBlockNumber(0).getTransactionProcessor();
blockHashLookup = new BlockHashLookup(genesisBlock.getHeader(), blockchain);
}
@Test
@ -87,7 +89,8 @@ public class TraceTransactionIntegrationTest {
createTransactionUpdater,
genesisBlock.getHeader(),
createTransaction,
genesisBlock.getHeader().getCoinbase());
genesisBlock.getHeader().getCoinbase(),
blockHashLookup);
assertThat(result.isSuccessful()).isTrue();
final Account createdContract =
createTransactionUpdater
@ -118,7 +121,8 @@ public class TraceTransactionIntegrationTest {
genesisBlock.getHeader(),
executeTransaction,
genesisBlock.getHeader().getCoinbase(),
tracer);
tracer,
blockHashLookup);
assertThat(result.isSuccessful()).isTrue();
@ -153,7 +157,8 @@ public class TraceTransactionIntegrationTest {
genesisBlock.getHeader(),
transaction,
genesisBlock.getHeader().getCoinbase(),
tracer);
tracer,
new BlockHashLookup(genesisBlock.getHeader(), blockchain));
final int expectedDepth = 0; // Reference impl returned 1. Why the difference?

@ -12,6 +12,7 @@ import net.consensys.pantheon.ethereum.core.Wei;
import net.consensys.pantheon.ethereum.core.WorldUpdater;
import net.consensys.pantheon.ethereum.mainnet.MainnetBlockProcessor.TransactionReceiptFactory;
import net.consensys.pantheon.ethereum.mainnet.TransactionProcessor;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import java.util.List;
import java.util.concurrent.CancellationException;
@ -146,10 +147,16 @@ public class BlockTransactionSelector {
}
final WorldUpdater worldStateUpdater = worldState.updater();
final BlockHashLookup blockHashLookup = new BlockHashLookup(processableBlockHeader, blockchain);
final TransactionProcessor.Result result =
transactionProcessor.processTransaction(
blockchain, worldStateUpdater, processableBlockHeader, transaction, miningBeneficiary);
blockchain,
worldStateUpdater,
processableBlockHeader,
transaction,
miningBeneficiary,
blockHashLookup);
if (!result.isInvalid()) {
worldStateUpdater.commit();

@ -11,6 +11,7 @@ import net.consensys.pantheon.ethereum.core.TransactionReceipt;
import net.consensys.pantheon.ethereum.core.Wei;
import net.consensys.pantheon.ethereum.core.WorldState;
import net.consensys.pantheon.ethereum.core.WorldUpdater;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import java.util.ArrayList;
import java.util.List;
@ -94,9 +95,7 @@ public class MainnetBlockProcessor implements BlockProcessor {
long gasUsed = 0;
final List<TransactionReceipt> receipts = new ArrayList<>();
for (int i = 0; i < transactions.size(); ++i) {
final Transaction transaction = transactions.get(i);
for (final Transaction transaction : transactions) {
final long remainingGasBudget = blockHeader.getGasLimit() - gasUsed;
if (Long.compareUnsigned(transaction.getGasLimit(), remainingGasBudget) > 0) {
LOG.warn(
@ -107,11 +106,17 @@ public class MainnetBlockProcessor implements BlockProcessor {
}
final WorldUpdater worldStateUpdater = worldState.updater();
final BlockHashLookup blockHashLookup = new BlockHashLookup(blockHeader, blockchain);
final Address miningBeneficiary =
miningBeneficiaryCalculator.calculateBeneficiary(blockHeader);
final TransactionProcessor.Result result =
transactionProcessor.processTransaction(
blockchain, worldStateUpdater, blockHeader, transaction, miningBeneficiary);
blockchain,
worldStateUpdater,
blockHeader,
transaction,
miningBeneficiary,
blockHashLookup);
if (result.isInvalid()) {
return Result.failed();
}
@ -131,10 +136,6 @@ public class MainnetBlockProcessor implements BlockProcessor {
return Result.successful(receipts);
}
protected Address calculateFeeRecipient(final BlockHeader header) {
return header.getCoinbase();
}
private boolean rewardCoinbase(
final MutableWorldState worldState,
final ProcessableBlockHeader header,

@ -11,6 +11,7 @@ import net.consensys.pantheon.ethereum.core.Transaction;
import net.consensys.pantheon.ethereum.core.Wei;
import net.consensys.pantheon.ethereum.core.WorldUpdater;
import net.consensys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import net.consensys.pantheon.ethereum.vm.Code;
import net.consensys.pantheon.ethereum.vm.GasCalculator;
import net.consensys.pantheon.ethereum.vm.MessageFrame;
@ -129,7 +130,8 @@ public class MainnetTransactionProcessor implements TransactionProcessor {
final ProcessableBlockHeader blockHeader,
final Transaction transaction,
final Address miningBenficiary,
final OperationTracer operationTracer) {
final OperationTracer operationTracer,
final BlockHashLookup blockHashLookup) {
LOG.trace("Starting execution of {}", transaction);
ValidationResult<TransactionInvalidReason> validationResult =
@ -199,6 +201,7 @@ public class MainnetTransactionProcessor implements TransactionProcessor {
.depth(0)
.completer(c -> {})
.miningBeneficiary(miningBenficiary)
.blockHashLookup(blockHashLookup)
.build();
} else {
@ -225,6 +228,7 @@ public class MainnetTransactionProcessor implements TransactionProcessor {
.depth(0)
.completer(c -> {})
.miningBeneficiary(miningBenficiary)
.blockHashLookup(blockHashLookup)
.build();
}

@ -9,6 +9,7 @@ import net.consensys.pantheon.ethereum.core.ProcessableBlockHeader;
import net.consensys.pantheon.ethereum.core.Transaction;
import net.consensys.pantheon.ethereum.core.WorldUpdater;
import net.consensys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import net.consensys.pantheon.ethereum.vm.OperationTracer;
import net.consensys.pantheon.util.bytes.BytesValue;
@ -99,9 +100,16 @@ public interface TransactionProcessor {
final WorldUpdater worldState,
final ProcessableBlockHeader blockHeader,
final Transaction transaction,
final Address miningBeneficiary) {
final Address miningBeneficiary,
final BlockHashLookup blockHashLookup) {
return processTransaction(
blockchain, worldState, blockHeader, transaction, miningBeneficiary, NO_TRACING);
blockchain,
worldState,
blockHeader,
transaction,
miningBeneficiary,
NO_TRACING,
blockHashLookup);
}
/**
@ -121,5 +129,6 @@ public interface TransactionProcessor {
ProcessableBlockHeader blockHeader,
Transaction transaction,
Address miningBeneficiary,
OperationTracer operationTracer);
OperationTracer operationTracer,
BlockHashLookup blockHashLookup);
}

@ -171,6 +171,7 @@ public abstract class AbstractCallOperation extends AbstractOperation {
.isStatic(isStatic(frame))
.completer(child -> complete(frame, child))
.miningBeneficiary(frame.getMiningBeneficiary())
.blockHashLookup(frame.getBlockHashLookup())
.build();
frame.getMessageFrameStack().addFirst(childFrame);

@ -0,0 +1,44 @@
package net.consensys.pantheon.ethereum.vm;
import net.consensys.pantheon.ethereum.chain.Blockchain;
import net.consensys.pantheon.ethereum.core.Hash;
import net.consensys.pantheon.ethereum.core.ProcessableBlockHeader;
import net.consensys.pantheon.ethereum.vm.operations.BlockHashOperation;
import java.util.HashMap;
import java.util.Map;
/**
* Calculates and caches block hashes by number following the chain for a specific branch. This is
* used by {@link BlockHashOperation} and ensures that the correct block hash is returned even when
* the block being imported is on a fork.
*
* <p>A new BlockHashCache must be created for each block being processed but should be reused for
* all transactions within that block.
*/
public class BlockHashLookup {
private ProcessableBlockHeader searchStartHeader;
private final Blockchain blockchain;
private final Map<Long, Hash> hashByNumber = new HashMap<>();
public BlockHashLookup(final ProcessableBlockHeader currentBlock, final Blockchain blockchain) {
this.searchStartHeader = currentBlock;
this.blockchain = blockchain;
hashByNumber.put(currentBlock.getNumber() - 1, currentBlock.getParentHash());
}
public Hash getBlockHash(final long blockNumber) {
final Hash cachedHash = hashByNumber.get(blockNumber);
if (cachedHash != null) {
return cachedHash;
}
while (searchStartHeader != null && searchStartHeader.getNumber() - 1 > blockNumber) {
searchStartHeader = blockchain.getBlockHeader(searchStartHeader.getParentHash()).orElse(null);
if (searchStartHeader != null) {
hashByNumber.put(searchStartHeader.getNumber() - 1, searchStartHeader.getParentHash());
}
}
return hashByNumber.getOrDefault(blockNumber, Hash.ZERO);
}
}

@ -175,6 +175,7 @@ public class MessageFrame {
// Machine state fields.
private Gas gasRemaining;
private final BlockHashLookup blockHashLookup;
private int pc;
private final Memory memory;
private final OperandStack stack;
@ -231,12 +232,14 @@ public class MessageFrame {
final int depth,
final boolean isStatic,
final Consumer<MessageFrame> completer,
final Address miningBeneficiary) {
final Address miningBeneficiary,
final BlockHashLookup blockHashLookup) {
this.type = type;
this.blockchain = blockchain;
this.messageFrameStack = messageFrameStack;
this.worldState = worldState;
this.gasRemaining = initialGas;
this.blockHashLookup = blockHashLookup;
this.pc = 0;
this.memory = new Memory();
this.stack = new PreAllocatedOperandStack(MAX_STACK_SIZE);
@ -783,6 +786,10 @@ public class MessageFrame {
return miningBeneficiary;
}
public BlockHashLookup getBlockHashLookup() {
return blockHashLookup;
}
public Operation getCurrentOperation() {
return currentOperation;
}
@ -812,6 +819,7 @@ public class MessageFrame {
private boolean isStatic = false;
private Consumer<MessageFrame> completer;
private Address miningBeneficiary;
private BlockHashLookup blockHashLookup;
public Builder type(final Type type) {
this.type = type;
@ -908,6 +916,11 @@ public class MessageFrame {
return this;
}
public Builder blockHashLookup(final BlockHashLookup blockHashLookup) {
this.blockHashLookup = blockHashLookup;
return this;
}
private void validate() {
checkState(type != null, "Missing message frame type");
checkState(blockchain != null, "Missing message frame blockchain");
@ -927,6 +940,7 @@ public class MessageFrame {
checkState(depth > -1, "Missing message frame depth");
checkState(completer != null, "Missing message frame completer");
checkState(miningBeneficiary != null, "Missing mining beneficiary");
checkState(blockHashLookup != null, "Missing block hash lookup");
}
public MessageFrame build() {
@ -951,7 +965,8 @@ public class MessageFrame {
depth,
isStatic,
completer,
miningBeneficiary);
miningBeneficiary,
blockHashLookup);
}
}
}

@ -109,6 +109,7 @@ public abstract class AbstractCreateOperation extends AbstractOperation {
.depth(frame.getMessageStackDepth() + 1)
.completer(child -> complete(frame, child))
.miningBeneficiary(frame.getMiningBeneficiary())
.blockHashLookup(frame.getBlockHashLookup())
.build();
frame.getMessageFrameStack().addFirst(childFrame);

@ -4,14 +4,14 @@ import net.consensys.pantheon.ethereum.core.Gas;
import net.consensys.pantheon.ethereum.core.Hash;
import net.consensys.pantheon.ethereum.core.ProcessableBlockHeader;
import net.consensys.pantheon.ethereum.vm.AbstractOperation;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import net.consensys.pantheon.ethereum.vm.GasCalculator;
import net.consensys.pantheon.ethereum.vm.MessageFrame;
import net.consensys.pantheon.util.bytes.Bytes32;
import net.consensys.pantheon.util.uint.UInt256;
import java.util.Optional;
public class BlockHashOperation extends AbstractOperation {
private static final int MAX_RELATIVE_BLOCK = 255;
public BlockHashOperation(final GasCalculator gasCalculator) {
@ -45,9 +45,9 @@ public class BlockHashOperation extends AbstractOperation {
|| soughtBlock > mostRecentBlockNumber) {
frame.pushStackItem(Bytes32.ZERO);
} else {
final Optional<Hash> maybeBlockHash = frame.getBlockchain().getBlockHashByNumber(soughtBlock);
assert maybeBlockHash.isPresent();
frame.pushStackItem(maybeBlockHash.get());
final BlockHashLookup blockHashLookup = frame.getBlockHashLookup();
final Hash blockHash = blockHashLookup.getBlockHash(soughtBlock);
frame.pushStackItem(blockHash);
}
}
}

@ -6,6 +6,7 @@ import net.consensys.pantheon.ethereum.mainnet.MainnetMessageCallProcessor;
import net.consensys.pantheon.ethereum.mainnet.PrecompileContractRegistry;
import net.consensys.pantheon.ethereum.mainnet.ProtocolSchedule;
import net.consensys.pantheon.ethereum.mainnet.ProtocolSpec;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import net.consensys.pantheon.ethereum.vm.Code;
import net.consensys.pantheon.ethereum.vm.MessageFrame;
import net.consensys.pantheon.ethereum.vm.MessageFrame.Type;
@ -68,6 +69,7 @@ public class TestCodeExecutor {
.depth(0)
.completer(c -> {})
.miningBeneficiary(blockHeader.coinbase)
.blockHashLookup(new BlockHashLookup(blockHeader, fixture.getBlockchain()))
.build();
messageFrameStack.addFirst(initialFrame);

@ -100,7 +100,8 @@ public class BlockTransactionSelectorTest {
final TransactionProcessor transactionProcessor = mock(TransactionProcessor.class);
when(transactionProcessor.processTransaction(any(), any(), any(), eq(transaction), any()))
when(transactionProcessor.processTransaction(
any(), any(), any(), eq(transaction), any(), any()))
.thenReturn(MainnetTransactionProcessor.Result.failed(5, ValidationResult.valid()));
final Blockchain blockchain = new TestBlockchain();
@ -153,7 +154,7 @@ public class BlockTransactionSelectorTest {
}
final TransactionProcessor transactionProcessor = mock(TransactionProcessor.class);
when(transactionProcessor.processTransaction(any(), any(), any(), any(), any()))
when(transactionProcessor.processTransaction(any(), any(), any(), any(), any(), any()))
.thenReturn(
MainnetTransactionProcessor.Result.successful(
new LogSeries(Lists.newArrayList()),
@ -161,7 +162,7 @@ public class BlockTransactionSelectorTest {
BytesValue.EMPTY,
ValidationResult.valid()));
when(transactionProcessor.processTransaction(
any(), any(), any(), eq(transactionsToInject.get(1)), any()))
any(), any(), any(), eq(transactionsToInject.get(1)), any(), any()))
.thenReturn(
MainnetTransactionProcessor.Result.invalid(ValidationResult.invalid(NONCE_TOO_LOW)));
@ -215,7 +216,7 @@ public class BlockTransactionSelectorTest {
pendingTransactions.addRemoteTransaction(tx);
}
final TransactionProcessor transactionProcessor = mock(TransactionProcessor.class);
when(transactionProcessor.processTransaction(any(), any(), any(), any(), any()))
when(transactionProcessor.processTransaction(any(), any(), any(), any(), any(), any()))
.thenReturn(
MainnetTransactionProcessor.Result.successful(
new LogSeries(Lists.newArrayList()),
@ -331,7 +332,7 @@ public class BlockTransactionSelectorTest {
// TransactionProcessor mock assumes all gas in the transaction was used (i.e. gasLimit).
final TransactionProcessor transactionProcessor = mock(TransactionProcessor.class);
when(transactionProcessor.processTransaction(any(), any(), any(), any(), any()))
when(transactionProcessor.processTransaction(any(), any(), any(), any(), any(), any()))
.thenReturn(
MainnetTransactionProcessor.Result.successful(
new LogSeries(Lists.newArrayList()),
@ -401,7 +402,7 @@ public class BlockTransactionSelectorTest {
// TransactionProcessor mock assumes all gas in the transaction was used (i.e. gasLimit).
final TransactionProcessor transactionProcessor = mock(TransactionProcessor.class);
when(transactionProcessor.processTransaction(any(), any(), any(), any(), any()))
when(transactionProcessor.processTransaction(any(), any(), any(), any(), any(), any()))
.thenReturn(
MainnetTransactionProcessor.Result.successful(
new LogSeries(Lists.newArrayList()),
@ -504,7 +505,12 @@ public class BlockTransactionSelectorTest {
pendingTransactions.addRemoteTransaction(invalidTransaction);
when(transactionProcessor.processTransaction(
eq(blockchain), any(WorldUpdater.class), eq(blockHeader), eq(validTransaction), any()))
eq(blockchain),
any(WorldUpdater.class),
eq(blockHeader),
eq(validTransaction),
any(),
any()))
.thenReturn(
Result.successful(
LogSeries.empty(), 10000, BytesValue.EMPTY, ValidationResult.valid()));
@ -513,6 +519,7 @@ public class BlockTransactionSelectorTest {
any(WorldUpdater.class),
eq(blockHeader),
eq(invalidTransaction),
any(),
any()))
.thenReturn(
Result.invalid(

@ -1,6 +1,7 @@
package net.consensys.pantheon.ethereum.core;
import net.consensys.pantheon.ethereum.mainnet.MainnetBlockHashFunction;
import net.consensys.pantheon.ethereum.vm.TestBlockchain;
import net.consensys.pantheon.util.bytes.BytesValue;
import net.consensys.pantheon.util.uint.UInt256;
@ -27,7 +28,7 @@ public class BlockHeaderMock extends BlockHeader {
@JsonProperty("currentNumber") final String number,
@JsonProperty("currentTimestamp") final String timestamp) {
super(
Hash.EMPTY, // parentHash
TestBlockchain.generateTestBlockHash(Long.decode(number) - 1),
Hash.EMPTY, // ommersHash
Address.fromHexString(coinbase),
Hash.EMPTY, // stateRoot

@ -0,0 +1,107 @@
package net.consensys.pantheon.ethereum.vm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import net.consensys.pantheon.ethereum.chain.Blockchain;
import net.consensys.pantheon.ethereum.core.BlockHeader;
import net.consensys.pantheon.ethereum.core.BlockHeaderTestFixture;
import net.consensys.pantheon.ethereum.core.Hash;
import java.util.Optional;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class BlockHashLookupTest {
private static final int CURRENT_BLOCK_NUMBER = 256;
private final Blockchain blockchain = mock(Blockchain.class);
private final BlockHeader[] headers = new BlockHeader[CURRENT_BLOCK_NUMBER];
private BlockHashLookup lookup;
@Before
public void setUp() {
BlockHeader parentHeader = null;
for (int i = 0; i < headers.length; i++) {
final BlockHeader header = createHeader(i, parentHeader);
when(blockchain.getBlockHeader(header.getHash())).thenReturn(Optional.of(header));
headers[i] = header;
parentHeader = headers[i];
}
lookup =
new BlockHashLookup(
createHeader(CURRENT_BLOCK_NUMBER, headers[headers.length - 1]), blockchain);
}
@After
public void verifyBlocksNeverLookedUpByNumber() {
// Looking up the block by number is incorrect because it always uses the canonical chain even
// if the block being imported is on a fork.
verify(blockchain, never()).getBlockHeader(anyLong());
}
@Test
public void shouldGetHashOfImmediateParent() {
assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 1);
}
@Test
public void shouldGetHashOfGenesisBlock() {
assertHashForBlockNumber(0);
}
@Test
public void shouldGetHashForRecentBlockAfterOlderBlock() {
assertHashForBlockNumber(10);
assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 1);
}
@Test
public void shouldReturnEmptyHashWhenRequestedBlockNotOnChain() {
assertThat(lookup.getBlockHash(CURRENT_BLOCK_NUMBER + 20)).isEqualTo(Hash.ZERO);
}
@Test
public void shouldReturnEmptyHashWhenParentBlockNotOnChain() {
final BlockHashLookup lookupWithUnavailableParent =
new BlockHashLookup(
new BlockHeaderTestFixture().number(CURRENT_BLOCK_NUMBER + 20).buildHeader(),
blockchain);
assertThat(lookupWithUnavailableParent.getBlockHash(CURRENT_BLOCK_NUMBER)).isEqualTo(Hash.ZERO);
}
@Test
public void shouldGetParentHashFromCurrentBlock() {
assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 1);
verifyZeroInteractions(blockchain);
}
@Test
public void shouldCacheBlockHashesWhileIteratingBackToPreviousHeader() {
assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 4);
assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 1);
verify(blockchain).getBlockHeader(headers[CURRENT_BLOCK_NUMBER - 1].getHash());
verify(blockchain).getBlockHeader(headers[CURRENT_BLOCK_NUMBER - 2].getHash());
verify(blockchain).getBlockHeader(headers[CURRENT_BLOCK_NUMBER - 3].getHash());
verifyNoMoreInteractions(blockchain);
}
private void assertHashForBlockNumber(final int blockNumber) {
assertThat(lookup.getBlockHash(blockNumber)).isEqualTo(headers[blockNumber].getHash());
}
private BlockHeader createHeader(final int blockNumber, final BlockHeader parentHeader) {
return new BlockHeaderTestFixture()
.number(blockNumber)
.parentHash(parentHeader != null ? parentHeader.getHash() : Hash.EMPTY)
.buildHeader();
}
}

@ -10,6 +10,7 @@ import static org.mockito.Mockito.when;
import net.consensys.pantheon.ethereum.core.Address;
import net.consensys.pantheon.ethereum.core.AddressHelpers;
import net.consensys.pantheon.ethereum.core.BlockHeader;
import net.consensys.pantheon.ethereum.core.BlockHeaderTestFixture;
import net.consensys.pantheon.ethereum.core.Gas;
import net.consensys.pantheon.ethereum.core.MutableAccount;
@ -189,10 +190,12 @@ public class DebugOperationTracerTest {
}
private MessageFrame.Builder validMessageFrameBuilder() {
final BlockHeader blockHeader = new BlockHeaderTestFixture().number(1).buildHeader();
final TestBlockchain blockchain = new TestBlockchain(blockHeader.getNumber());
return MessageFrame.builder()
.type(MessageFrame.Type.MESSAGE_CALL)
.messageFrameStack(new ArrayDeque<>())
.blockchain(new TestBlockchain())
.blockchain(blockchain)
.worldState(worldUpdater)
.initialGas(INITIAL_GAS)
.contract(calculateAddressWithRespectTo(Address.ID, 1))
@ -204,10 +207,11 @@ public class DebugOperationTracerTest {
.value(Wei.of(30))
.apparentValue(Wei.of(35))
.code(new Code())
.blockHeader(new BlockHeaderTestFixture().buildHeader())
.blockHeader(blockHeader)
.depth(DEPTH)
.completer(c -> {})
.miningBeneficiary(AddressHelpers.ofValue(0));
.miningBeneficiary(AddressHelpers.ofValue(0))
.blockHashLookup(new BlockHashLookup(blockHeader, blockchain));
}
private Map<UInt256, UInt256> setupStorageForCapture(final MessageFrame frame) {

@ -94,13 +94,15 @@ public class GeneralStateReferenceTestTools {
final TransactionProcessor processor = transactionProcessor(spec.eip());
final WorldUpdater worldStateUpdater = worldState.updater();
final TestBlockchain blockchain = new TestBlockchain(blockHeader.getNumber());
final TransactionProcessor.Result result =
processor.processTransaction(
new TestBlockchain(),
blockchain,
worldStateUpdater,
blockHeader,
transaction,
blockHeader.getCoinbase());
blockHeader.getCoinbase(),
new BlockHashLookup(blockHeader, blockchain));
if (!result.isInvalid()) {
worldStateUpdater.commit();

@ -8,13 +8,16 @@ import net.consensys.pantheon.ethereum.chain.ChainHead;
import net.consensys.pantheon.ethereum.chain.TransactionLocation;
import net.consensys.pantheon.ethereum.core.BlockBody;
import net.consensys.pantheon.ethereum.core.BlockHeader;
import net.consensys.pantheon.ethereum.core.BlockHeaderTestFixture;
import net.consensys.pantheon.ethereum.core.Hash;
import net.consensys.pantheon.ethereum.core.Transaction;
import net.consensys.pantheon.ethereum.core.TransactionReceipt;
import net.consensys.pantheon.util.bytes.BytesValue;
import net.consensys.pantheon.util.uint.UInt256;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
@ -26,7 +29,29 @@ import java.util.Optional;
*/
public class TestBlockchain implements Blockchain {
private static Hash generateTestBlockHash(final long number) {
// Maximum number of blocks prior to the chain head that can be retrieved by hash.
private static final long MAXIMUM_BLOCKS_BEHIND_HEAD = 256;
private final Map<Hash, BlockHeader> hashToHeader = new HashMap<>();
public TestBlockchain() {
this(0);
}
public TestBlockchain(final long chainHeadBlockNumber) {
for (long blockNumber = Math.max(0L, chainHeadBlockNumber - MAXIMUM_BLOCKS_BEHIND_HEAD);
blockNumber < chainHeadBlockNumber;
blockNumber++) {
final Hash hash = generateTestBlockHash(blockNumber);
hashToHeader.put(
hash,
new BlockHeaderTestFixture()
.number(blockNumber)
.parentHash(generateTestBlockHash(blockNumber - 1))
.buildHeader());
}
}
public static Hash generateTestBlockHash(final long number) {
final byte[] bytes = Long.toString(number).getBytes(UTF_8);
return Hash.hash(BytesValue.wrap(bytes));
}
@ -63,7 +88,7 @@ public class TestBlockchain implements Blockchain {
@Override
public Optional<BlockHeader> getBlockHeader(final Hash blockHeaderHash) {
throw new UnsupportedOperationException();
return Optional.ofNullable(hashToHeader.get(blockHeaderHash));
}
@Override

@ -101,11 +101,12 @@ public class VMReferenceTest extends AbstractRetryingTest {
final ProtocolSpec<Void> protocolSpec =
MainnetProtocolSpecs.frontier(new MutableProtocolSchedule<>());
final TestBlockchain blockchain = new TestBlockchain(execEnv.getBlockHeader().getNumber());
final MessageFrame frame =
MessageFrame.builder()
.type(MessageFrame.Type.MESSAGE_CALL)
.messageFrameStack(new ArrayDeque<>())
.blockchain(new TestBlockchain())
.blockchain(blockchain)
.worldState(worldState.updater())
.initialGas(spec.getExec().getGas())
.contract(execEnv.getAccountAddress())
@ -121,6 +122,7 @@ public class VMReferenceTest extends AbstractRetryingTest {
.depth(execEnv.getDepth())
.completer(c -> {})
.miningBeneficiary(execEnv.getBlockHeader().getCoinbase())
.blockHashLookup(new BlockHashLookup(execEnv.getBlockHeader(), blockchain))
.build();
// This is normally set inside the containing message executing the code.

@ -6,6 +6,7 @@ import static org.mockito.Mockito.mock;
import net.consensys.pantheon.ethereum.chain.Blockchain;
import net.consensys.pantheon.ethereum.core.Address;
import net.consensys.pantheon.ethereum.core.AddressHelpers;
import net.consensys.pantheon.ethereum.core.BlockHeader;
import net.consensys.pantheon.ethereum.core.BlockHeaderTestFixture;
import net.consensys.pantheon.ethereum.core.Gas;
import net.consensys.pantheon.ethereum.core.Hash;
@ -13,6 +14,7 @@ import net.consensys.pantheon.ethereum.core.Wei;
import net.consensys.pantheon.ethereum.core.WorldUpdater;
import net.consensys.pantheon.ethereum.db.WorldStateArchive;
import net.consensys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import net.consensys.pantheon.ethereum.vm.Code;
import net.consensys.pantheon.ethereum.vm.MessageFrame;
import net.consensys.pantheon.ethereum.vm.MessageFrame.Type;
@ -104,6 +106,7 @@ public class ExtCodeHashOperationTest {
}
private MessageFrame createMessageFrame(final Bytes32 stackItem) {
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
final MessageFrame frame =
new MessageFrame.Builder()
.type(Type.MESSAGE_CALL)
@ -117,13 +120,14 @@ public class ExtCodeHashOperationTest {
.sender(ADDRESS1)
.worldState(worldStateUpdater)
.messageFrameStack(new ArrayDeque<>())
.blockHeader(new BlockHeaderTestFixture().buildHeader())
.blockHeader(blockHeader)
.value(Wei.ZERO)
.apparentValue(Wei.ZERO)
.code(new Code(BytesValue.EMPTY))
.blockchain(blockchain)
.completer(messageFrame -> {})
.miningBeneficiary(AddressHelpers.ofValue(0))
.blockHashLookup(new BlockHashLookup(blockHeader, blockchain))
.build();
frame.pushStackItem(stackItem);

@ -10,6 +10,7 @@ import net.consensys.pantheon.ethereum.db.WorldStateArchive;
import net.consensys.pantheon.ethereum.mainnet.ProtocolSchedule;
import net.consensys.pantheon.ethereum.mainnet.ProtocolSpec;
import net.consensys.pantheon.ethereum.mainnet.TransactionProcessor;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import java.util.Optional;
@ -46,6 +47,7 @@ public class BlockReplay {
}
final MutableWorldState mutableWorldState =
worldStateArchive.getMutable(previous.getStateRoot());
final BlockHashLookup blockHashLookup = new BlockHashLookup(header, blockchain);
for (final Transaction transaction : body.getTransactions()) {
if (transaction.hash().equals(transactionHash)) {
return Optional.of(
@ -58,7 +60,8 @@ public class BlockReplay {
mutableWorldState.updater(),
header,
transaction,
spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header));
spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header),
blockHashLookup);
}
}
return Optional.empty();
@ -76,7 +79,8 @@ public class BlockReplay {
worldState.updater(),
blockHeader,
transaction,
spec.getMiningBeneficiaryCalculator().calculateBeneficiary(blockHeader));
spec.getMiningBeneficiaryCalculator().calculateBeneficiary(blockHeader),
new BlockHashLookup(blockHeader, blockchain));
return action.performAction(
transaction, blockHeader, blockchain, worldState, transactionProcessor);
});

@ -2,6 +2,7 @@ package net.consensys.pantheon.ethereum.jsonrpc.internal.processor;
import net.consensys.pantheon.ethereum.core.Hash;
import net.consensys.pantheon.ethereum.mainnet.TransactionProcessor.Result;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import net.consensys.pantheon.ethereum.vm.DebugOperationTracer;
import java.util.Optional;
@ -28,7 +29,8 @@ public class TransactionTracer {
header,
transaction,
header.getCoinbase(),
tracer);
tracer,
new BlockHashLookup(header, blockchain));
return new TransactionTrace(transaction, result, tracer.getTraceFrames());
});
}

@ -13,6 +13,7 @@ import net.consensys.pantheon.ethereum.jsonrpc.internal.parameters.CallParameter
import net.consensys.pantheon.ethereum.mainnet.ProtocolSchedule;
import net.consensys.pantheon.ethereum.mainnet.ProtocolSpec;
import net.consensys.pantheon.ethereum.mainnet.TransactionProcessor;
import net.consensys.pantheon.ethereum.vm.BlockHashLookup;
import net.consensys.pantheon.util.bytes.BytesValue;
import java.util.Optional;
@ -88,7 +89,8 @@ public class TransientTransactionProcessor {
worldState.updater(),
header,
transaction,
protocolSpec.getMiningBeneficiaryCalculator().calculateBeneficiary(header));
protocolSpec.getMiningBeneficiaryCalculator().calculateBeneficiary(header),
new BlockHashLookup(header, blockchain));
return Optional.of(new TransientTransactionProcessingResult(transaction, result));
}

@ -215,13 +215,14 @@ public class TransientTransactionProcessorTest {
break;
}
when(transactionProcessor.processTransaction(any(), any(), any(), eq(transaction), any()))
when(transactionProcessor.processTransaction(
any(), any(), any(), eq(transaction), any(), any()))
.thenReturn(result);
}
private void verifyTransactionWasProcessed(final Transaction expectedTransaction) {
verify(transactionProcessor)
.processTransaction(any(), any(), any(), eq(expectedTransaction), any());
.processTransaction(any(), any(), any(), eq(expectedTransaction), any(), any());
}
private CallParameter callParameter() {

@ -1,15 +1,19 @@
package net.consensys.pantheon.ethereum.jsonrpc.internal.processor;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import net.consensys.pantheon.ethereum.chain.Blockchain;
import net.consensys.pantheon.ethereum.core.Address;
import net.consensys.pantheon.ethereum.core.BlockBody;
import net.consensys.pantheon.ethereum.core.BlockHeader;
import net.consensys.pantheon.ethereum.core.Hash;
import net.consensys.pantheon.ethereum.core.MutableWorldState;
import net.consensys.pantheon.ethereum.core.Transaction;
import net.consensys.pantheon.ethereum.core.WorldUpdater;
import net.consensys.pantheon.ethereum.db.WorldStateArchive;
import net.consensys.pantheon.ethereum.debug.TraceFrame;
import net.consensys.pantheon.ethereum.mainnet.ProtocolSchedule;
@ -135,13 +139,16 @@ public class TransactionTracerTest {
when(blockBody.getTransactions()).thenReturn(Collections.singletonList(transaction));
when(blockchain.getBlockBody(blockHash)).thenReturn(Optional.of(blockBody));
final WorldUpdater updater = mutableWorldState.updater();
final Address coinbase = blockHeader.getCoinbase();
when(transactionProcessor.processTransaction(
blockchain,
mutableWorldState.updater(),
blockHeader,
transaction,
blockHeader.getCoinbase(),
tracer))
eq(blockchain),
eq(updater),
eq(blockHeader),
eq(transaction),
eq(coinbase),
eq(tracer),
any()))
.thenReturn(result);
final Optional<TransactionTrace> transactionTrace =

Loading…
Cancel
Save