mirror of https://github.com/hyperledger/besu
EIP-2935: Serve historical block hashes from state (#7080)
* EIP-2935: Serve historical block hashes from state Refactor the BlockHashOperation to move hash lookup into the ProtocolSpec, and combine logic with beacon root storage. Update t8n test to use block hash list in new format Signed-off-by: Danno Ferrin <danno@numisight.com> Author: Gabriel-Trintinalia gabriel.trintinalia@consensys.net Co-authored-by: Danno Ferrin danno@numisight.com Co-authored-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>pull/7092/head
parent
2db3705b1d
commit
821daf12b9
@ -0,0 +1,31 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* 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.mainnet.blockhash; |
||||
|
||||
import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; |
||||
|
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; |
||||
|
||||
public interface BlockHashProcessor { |
||||
|
||||
void processBlockHashes( |
||||
Blockchain blockchain, |
||||
MutableWorldState worldState, |
||||
ProcessableBlockHeader currentBlockHeader); |
||||
|
||||
BlockHashLookup getBlockHashLookup(ProcessableBlockHeader currentHeader, Blockchain blockchain); |
||||
} |
@ -0,0 +1,52 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* 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.mainnet.blockhash; |
||||
|
||||
import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; |
||||
|
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.ethereum.mainnet.ParentBeaconBlockRootHelper; |
||||
import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; |
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; |
||||
|
||||
/** Processes the beacon block storage if it is present in the block header. */ |
||||
public class CancunBlockHashProcessor implements BlockHashProcessor { |
||||
|
||||
@Override |
||||
public BlockHashLookup getBlockHashLookup( |
||||
final ProcessableBlockHeader currentHeader, final Blockchain blockchain) { |
||||
return new CachingBlockHashLookup(currentHeader, blockchain); |
||||
} |
||||
|
||||
@Override |
||||
public void processBlockHashes( |
||||
final Blockchain blockchain, |
||||
final MutableWorldState mutableWorldState, |
||||
final ProcessableBlockHeader currentBlockHeader) { |
||||
currentBlockHeader |
||||
.getParentBeaconBlockRoot() |
||||
.ifPresent( |
||||
beaconBlockRoot -> { |
||||
if (!beaconBlockRoot.isEmpty()) { |
||||
WorldUpdater worldUpdater = mutableWorldState.updater(); |
||||
ParentBeaconBlockRootHelper.storeParentBeaconBlockRoot( |
||||
worldUpdater, currentBlockHeader.getTimestamp(), beaconBlockRoot); |
||||
worldUpdater.commit(); |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* 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.mainnet.blockhash; |
||||
|
||||
import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; |
||||
|
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; |
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; |
||||
|
||||
public class FrontierBlockHashProcessor implements BlockHashProcessor { |
||||
@Override |
||||
public void processBlockHashes( |
||||
final Blockchain blockchain, |
||||
final MutableWorldState mutableWorldState, |
||||
final ProcessableBlockHeader currentBlockHeader) { |
||||
// do nothing
|
||||
} |
||||
|
||||
@Override |
||||
public BlockHashLookup getBlockHashLookup( |
||||
final ProcessableBlockHeader currentHeader, final Blockchain blockchain) { |
||||
return new CachingBlockHashLookup(currentHeader, blockchain); |
||||
} |
||||
} |
@ -0,0 +1,153 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* 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.mainnet.blockhash; |
||||
|
||||
import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import org.apache.tuweni.units.bigints.UInt256; |
||||
|
||||
/** |
||||
* Processes and stores historical block hashes in accordance with EIP-2935. This class is |
||||
* responsible for managing the storage of block hashes to support EIP-2935, which introduces |
||||
* historical block hash access in smart contracts. |
||||
*/ |
||||
public class PragueBlockHashProcessor extends CancunBlockHashProcessor { |
||||
|
||||
public static final Address HISTORY_STORAGE_ADDRESS = |
||||
Address.fromHexString("0x25a219378dad9b3503c8268c9ca836a52427a4fb"); |
||||
|
||||
/** The HISTORY_SERVE_WINDOW */ |
||||
public static final long HISTORY_SERVE_WINDOW = 8192; |
||||
|
||||
private final long forkTimestamp; |
||||
private final long historySaveWindow; |
||||
private final Address historyStorageAddress; |
||||
|
||||
/** |
||||
* Constructs a BlockHashProcessor with a specified fork timestamp. |
||||
* |
||||
* @param forkTimestamp The timestamp at which the fork becomes active. |
||||
*/ |
||||
public PragueBlockHashProcessor(final long forkTimestamp) { |
||||
this(forkTimestamp, HISTORY_STORAGE_ADDRESS, HISTORY_SERVE_WINDOW); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a BlockHashProcessor with a specified fork timestamp and history save window. This |
||||
* constructor is primarily used for testing. |
||||
* |
||||
* @param forkTimestamp The timestamp at which the fork becomes active. |
||||
* @param historyStorageAddress the address of the contract storing the history |
||||
* @param historySaveWindow The number of blocks for which history should be saved. |
||||
*/ |
||||
@VisibleForTesting |
||||
public PragueBlockHashProcessor( |
||||
final long forkTimestamp, final Address historyStorageAddress, final long historySaveWindow) { |
||||
this.forkTimestamp = forkTimestamp; |
||||
this.historyStorageAddress = historyStorageAddress; |
||||
this.historySaveWindow = historySaveWindow; |
||||
} |
||||
|
||||
@Override |
||||
public BlockHashLookup getBlockHashLookup( |
||||
final ProcessableBlockHeader currentHeader, final Blockchain blockchain) { |
||||
return (frame, blockNumber) -> { |
||||
long currentBlockNumber = frame.getBlockValues().getNumber(); |
||||
if (currentBlockNumber <= blockNumber |
||||
|| currentBlockNumber - blockNumber >= historySaveWindow |
||||
|| blockNumber < 0) { |
||||
return Hash.ZERO; |
||||
} |
||||
return Hash.wrap( |
||||
frame |
||||
.getWorldUpdater() |
||||
.get(historyStorageAddress) |
||||
.getStorageValue(UInt256.valueOf(blockNumber % historySaveWindow))); |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public void processBlockHashes( |
||||
final Blockchain blockchain, |
||||
final MutableWorldState mutableWorldState, |
||||
final ProcessableBlockHeader currentBlockHeader) { |
||||
super.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); |
||||
|
||||
WorldUpdater worldUpdater = mutableWorldState.updater(); |
||||
final MutableAccount historyStorageAccount = worldUpdater.getOrCreate(HISTORY_STORAGE_ADDRESS); |
||||
|
||||
if (currentBlockHeader.getNumber() > 0) { |
||||
storeParentHash(historyStorageAccount, currentBlockHeader); |
||||
|
||||
BlockHeader ancestor = |
||||
blockchain.getBlockHeader(currentBlockHeader.getParentHash()).orElseThrow(); |
||||
|
||||
// If fork block, add the parent's direct `HISTORY_SERVE_WINDOW - 1`
|
||||
if (ancestor.getTimestamp() < forkTimestamp) { |
||||
for (int i = 0; i < (historySaveWindow - 1) && ancestor.getNumber() > 0; i++) { |
||||
ancestor = blockchain.getBlockHeader(ancestor.getParentHash()).orElseThrow(); |
||||
storeBlockHeaderHash(historyStorageAccount, ancestor); |
||||
} |
||||
} |
||||
} |
||||
worldUpdater.commit(); |
||||
} |
||||
|
||||
/** |
||||
* Stores the hash of the parent block in the world state. |
||||
* |
||||
* @param account The account associated with the historical block hash storage. |
||||
* @param header The current block header being processed. |
||||
*/ |
||||
private void storeParentHash(final MutableAccount account, final ProcessableBlockHeader header) { |
||||
storeHash(account, header.getNumber() - 1, header.getParentHash()); |
||||
} |
||||
|
||||
/** |
||||
* Stores the hash of a block in the world state. |
||||
* |
||||
* @param account The account associated with the historical block hash storage. |
||||
* @param header The block header whose hash is to be stored. |
||||
*/ |
||||
private void storeBlockHeaderHash(final MutableAccount account, final BlockHeader header) { |
||||
storeHash(account, header.getNumber(), header.getHash()); |
||||
} |
||||
|
||||
/** |
||||
* Stores the hash in the world state. |
||||
* |
||||
* @param account The account associated with the historical block hash storage. |
||||
* @param number The slot to store. |
||||
* @param hash The hash to be stored. |
||||
*/ |
||||
private void storeHash(final MutableAccount account, final long number, final Hash hash) { |
||||
System.out.printf( |
||||
"Writing to %s %s=%s%n", |
||||
account.getAddress(), |
||||
UInt256.valueOf(number % historySaveWindow).toDecimalString(), |
||||
UInt256.fromBytes(hash).toHexString()); |
||||
account.setStorageValue(UInt256.valueOf(number % historySaveWindow), UInt256.fromBytes(hash)); |
||||
} |
||||
} |
@ -1,30 +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.vm; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.evm.operation.BlockHashOperation; |
||||
|
||||
import java.util.function.Function; |
||||
|
||||
/** |
||||
* 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 interface BlockHashLookup extends Function<Long, Hash> {} |
@ -0,0 +1,187 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* 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.mainnet.blockhash; |
||||
|
||||
import static org.hyperledger.besu.datatypes.Hash.fromHexStringLenient; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.times; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.units.bigints.UInt256; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
class BlockHashProcessorTest { |
||||
private Blockchain blockchain; |
||||
private WorldUpdater worldUpdater; |
||||
private MutableWorldState mutableWorldState; |
||||
private MutableAccount account; |
||||
private BlockHashProcessor processor; |
||||
|
||||
private long historicalWindow = 8192; |
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
blockchain = mock(Blockchain.class); |
||||
mutableWorldState = mock(MutableWorldState.class); |
||||
worldUpdater = mock(WorldUpdater.class); |
||||
account = mock(MutableAccount.class); |
||||
when(mutableWorldState.updater()).thenReturn(worldUpdater); |
||||
when(worldUpdater.getOrCreate(PragueBlockHashProcessor.HISTORY_STORAGE_ADDRESS)) |
||||
.thenReturn(account); |
||||
} |
||||
|
||||
@Test |
||||
void shouldStoreParentBlockHash() { |
||||
long forkTimestamp = 0; |
||||
long currentBlock = 3; |
||||
processor = new PragueBlockHashProcessor(forkTimestamp); |
||||
BlockHeader currentBlockHeader = mockBlockHeader(currentBlock); |
||||
mockAncestorHeaders(currentBlockHeader, 3); |
||||
processor.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); |
||||
// only parent slot number must be set
|
||||
verify(account, times(1)).setStorageValue(any(), any()); |
||||
verifyAccount(currentBlock - 1, historicalWindow); |
||||
} |
||||
|
||||
@Test |
||||
void shouldNotStoreBlockHashForGenesisBlock() { |
||||
// For the fork to be activated at genesis, no history is written to the genesis state, and at
|
||||
// the start of block 1, genesis hash will be written as a normal operation to slot 0.
|
||||
long forkTimestamp = 0; |
||||
long currentBlock = 0; |
||||
processor = new PragueBlockHashProcessor(forkTimestamp); |
||||
BlockHeader currentBlockHeader = mockBlockHeader(currentBlock); |
||||
mockAncestorHeaders(currentBlockHeader, 0); |
||||
|
||||
processor.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); |
||||
verifyNoInteractions(account); |
||||
} |
||||
|
||||
@Test |
||||
void shouldStoreAncestorBlockHashesAtForkCorrectlyParentIsGenesis() { |
||||
// for activation at block 1, only genesis hash will be written at slot 0 as there is no
|
||||
// additional history that needs to be persisted.
|
||||
long forkTimestamp = 1; |
||||
long currentBlock = 1; |
||||
processor = new PragueBlockHashProcessor(forkTimestamp); |
||||
BlockHeader currentBlockHeader = mockBlockHeader(currentBlock); |
||||
mockAncestorHeaders(currentBlockHeader, 10); |
||||
|
||||
processor.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); |
||||
verify(account, times(1)).setStorageValue(any(), any()); |
||||
verifyAccount(0, historicalWindow); |
||||
} |
||||
|
||||
@Test |
||||
void shouldStoreAncestorBlockHashesAtForkCorrectly() { |
||||
// for activation at block 32, block 31’s hash will be written to slot 31 and additional history
|
||||
// for 0..30’s hashes will be persisted, so all in all 0..31’s hashes.
|
||||
long forkTimestamp = 32; |
||||
long currentBlock = 32; |
||||
processor = new PragueBlockHashProcessor(forkTimestamp); |
||||
BlockHeader currentBlockHeader = mockBlockHeader(currentBlock); |
||||
mockAncestorHeaders(currentBlockHeader, 32); |
||||
|
||||
processor.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); |
||||
verifyAncestor(currentBlock, 32, historicalWindow); |
||||
} |
||||
|
||||
@Test |
||||
void shouldStoreAncestorBlockHashesAtForkCorrectlyMaxWindows() { |
||||
long forkTimestamp = 10000; |
||||
long currentBlock = 10000; |
||||
historicalWindow = 8192; |
||||
processor = |
||||
new PragueBlockHashProcessor( |
||||
forkTimestamp, PragueBlockHashProcessor.HISTORY_STORAGE_ADDRESS, historicalWindow); |
||||
BlockHeader currentBlockHeader = mockBlockHeader(currentBlock); |
||||
mockAncestorHeaders(currentBlockHeader, 10000); |
||||
processor.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); |
||||
|
||||
// Total of historicalWindow hashes were stored
|
||||
verify(account, times((int) historicalWindow)).setStorageValue(any(), any()); |
||||
|
||||
// for activation at block 10000, block 1808-9999’s hashes will be presisted in the slot
|
||||
verifyAccount(1808, historicalWindow); |
||||
verifyAccount(9999, historicalWindow); |
||||
// BLOCKHASH for 1807 or less would resolve to 0 as only HISTORY_SERVE_WINDOW are persisted.
|
||||
verifyAccountNoIteraction(1807, historicalWindow); |
||||
verifyAccountNoIteraction(10000, historicalWindow); |
||||
} |
||||
|
||||
@Test |
||||
void shouldWriteGenesisHashAtSlot0() { |
||||
processor = new PragueBlockHashProcessor(0); |
||||
BlockHeader header = mockBlockHeader(1); |
||||
mockAncestorHeaders(header, 1); |
||||
processor.processBlockHashes(blockchain, mutableWorldState, header); |
||||
verify(account) |
||||
.setStorageValue(UInt256.valueOf(0), UInt256.fromHexString(Hash.ZERO.toHexString())); |
||||
} |
||||
|
||||
private void verifyAncestor( |
||||
final long blockNumber, final int count, final long historicalWindow) { |
||||
int totalTouchedSlots = (int) (blockNumber - count <= 0 ? blockNumber : count); |
||||
long firstAncestor = Math.max(blockNumber - count - 1, 0); |
||||
verify(account, times(totalTouchedSlots)).setStorageValue(any(), any()); |
||||
for (long i = firstAncestor; i < blockNumber; i++) { |
||||
verifyAccount(i, historicalWindow); |
||||
} |
||||
} |
||||
|
||||
private void verifyAccount(final long number, final long historicalWindow) { |
||||
verify(account) |
||||
.setStorageValue(UInt256.valueOf(number % historicalWindow), UInt256.valueOf(number)); |
||||
} |
||||
|
||||
private void verifyAccountNoIteraction(final long number, final long historicalWindow) { |
||||
verify(account, times(0)) |
||||
.setStorageValue(UInt256.valueOf(number % historicalWindow), UInt256.valueOf(number)); |
||||
} |
||||
|
||||
private void mockAncestorHeaders(final BlockHeader blockHeader, final int count) { |
||||
long firstAncestor = Math.max(blockHeader.getNumber() - count, 0); |
||||
var block = blockHeader; |
||||
for (long i = blockHeader.getNumber(); i > firstAncestor; i--) { |
||||
long parentNumber = block.getNumber() - 1; |
||||
block = mockBlockHeader(parentNumber); |
||||
} |
||||
} |
||||
|
||||
private BlockHeader mockBlockHeader(final long currentNumber) { |
||||
BlockHeader blockHeader = mock(BlockHeader.class); |
||||
when(blockHeader.getNumber()).thenReturn(currentNumber); |
||||
Hash hash = fromHexStringLenient("0x" + Long.toHexString(currentNumber)); |
||||
Hash parentHash = fromHexStringLenient("0x" + Long.toHexString(currentNumber - 1)); |
||||
when(blockHeader.getHash()).thenReturn(hash); |
||||
when(blockHeader.getTimestamp()).thenReturn(currentNumber); |
||||
when(blockHeader.getParentHash()).thenReturn(parentHash); |
||||
when(blockchain.getBlockHeader(hash)).thenReturn(Optional.of(blockHeader)); |
||||
return blockHeader; |
||||
} |
||||
} |
@ -0,0 +1,206 @@ |
||||
/* |
||||
* 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.evmtool.t8n; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.chain.BlockAddedObserver; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.chain.ChainHead; |
||||
import org.hyperledger.besu.ethereum.chain.ChainReorgObserver; |
||||
import org.hyperledger.besu.ethereum.chain.TransactionLocation; |
||||
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; |
||||
import org.hyperledger.besu.ethereum.core.Difficulty; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
import org.hyperledger.besu.ethereum.core.TransactionReceipt; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; |
||||
import org.hyperledger.besu.ethereum.referencetests.ReferenceTestEnv; |
||||
|
||||
import java.util.Comparator; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
/** |
||||
* A blockchain mock for the Ethereum reference tests. |
||||
* |
||||
* <p>Operations which would lead to non-deterministic behaviour if executed while processing |
||||
* transactions throw {@link NonDeterministicOperationException}. For example all methods that |
||||
* lookup blocks by number since the block being processed may not be on the canonical chain but |
||||
* that must not affect the execution of its transactions. |
||||
* |
||||
* <p>The Ethereum reference tests for VM execution (VMTests) and transaction processing |
||||
* (GeneralStateTests) require a block's hash to be to be the hash of the string of it's block |
||||
* number. |
||||
*/ |
||||
public class T8nBlockchain implements Blockchain { |
||||
|
||||
// Maximum number of blocks prior to the chain head that can be retrieved by hash.
|
||||
private static final String NUMBER_LOOKUP_ERROR = |
||||
"Blocks must not be looked up by number in the EVM. The block being processed may not be on the canonical chain."; |
||||
private static final String CHAIN_HEAD_ERROR = |
||||
"Chain head is inherently non-deterministic. The block currently being processed should be treated as the chain head."; |
||||
private static final String FINALIZED_ERROR = |
||||
"Finalized block is inherently non-deterministic. The block currently being processed should be treated as the finalized block."; |
||||
private static final String SAFE_BLOCK_ERROR = |
||||
"Safe block is inherently non-deterministic. The block currently being processed should be treated as the safe block."; |
||||
private final Map<Hash, BlockHeader> hashToHeader = new HashMap<>(); |
||||
private final BlockHeader chainHeader; |
||||
|
||||
/** |
||||
* Create a new blockchain object for T8n testing. |
||||
* |
||||
* @param referenceTestEnv the referenfe test environment, containing most of the block header |
||||
* stuff |
||||
* @param protocolSpec the protocol spec, which impacts what block header fields are implemente. |
||||
*/ |
||||
public T8nBlockchain(final ReferenceTestEnv referenceTestEnv, final ProtocolSpec protocolSpec) { |
||||
|
||||
Map<Long, Hash> blockHashes = referenceTestEnv.getBlockHashes(); |
||||
|
||||
chainHeader = referenceTestEnv.parentBlockHeader(protocolSpec); |
||||
|
||||
blockHashes.forEach( |
||||
(num, blockHash) -> |
||||
hashToHeader.put( |
||||
blockHash, |
||||
BlockHeaderBuilder.createDefault() |
||||
.number(num) |
||||
.parentHash(blockHashes.getOrDefault(num - 1, Hash.ZERO)) |
||||
.timestamp(0) |
||||
.coinbase(chainHeader.getCoinbase()) |
||||
.difficulty(chainHeader.getDifficulty()) |
||||
.gasLimit(chainHeader.getGasLimit()) |
||||
.buildBlockHeader())); |
||||
hashToHeader.put(referenceTestEnv.getParentHash(), chainHeader); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Hash> getBlockHashByNumber(final long number) { |
||||
throw new NonDeterministicOperationException(NUMBER_LOOKUP_ERROR); |
||||
} |
||||
|
||||
@Override |
||||
public ChainHead getChainHead() { |
||||
return new ChainHead(chainHeader, chainHeader.getDifficulty(), chainHeader.getNumber()); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Hash> getFinalized() { |
||||
throw new NonDeterministicOperationException(FINALIZED_ERROR); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Hash> getSafeBlock() { |
||||
throw new NonDeterministicOperationException(SAFE_BLOCK_ERROR); |
||||
} |
||||
|
||||
@Override |
||||
public long getChainHeadBlockNumber() { |
||||
throw new NonDeterministicOperationException(CHAIN_HEAD_ERROR); |
||||
} |
||||
|
||||
@Override |
||||
public Hash getChainHeadHash() { |
||||
return chainHeader.getHash(); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<TransactionLocation> getTransactionLocation(final Hash transactionHash) { |
||||
throw new NonDeterministicOperationException("Transaction location may be different on forks"); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<BlockHeader> getBlockHeader(final long blockNumber) { |
||||
throw new NonDeterministicOperationException(NUMBER_LOOKUP_ERROR); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<BlockHeader> getBlockHeader(final Hash blockHeaderHash) { |
||||
return Optional.ofNullable(hashToHeader.get(blockHeaderHash)); |
||||
} |
||||
|
||||
@Override |
||||
public synchronized Optional<BlockHeader> getBlockHeaderSafe(final Hash blockHeaderHash) { |
||||
return Optional.ofNullable(hashToHeader.get(blockHeaderHash)); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<BlockBody> getBlockBody(final Hash blockHeaderHash) { |
||||
// Deterministic, but just not implemented.
|
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<List<TransactionReceipt>> getTxReceipts(final Hash blockHeaderHash) { |
||||
// Deterministic, but just not implemented.
|
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Difficulty> getTotalDifficultyByHash(final Hash blockHeaderHash) { |
||||
// Deterministic, but just not implemented.
|
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Transaction> getTransactionByHash(final Hash transactionHash) { |
||||
throw new NonDeterministicOperationException( |
||||
"Which transactions are on the chain may vary on different forks"); |
||||
} |
||||
|
||||
@Override |
||||
public long observeBlockAdded(final BlockAddedObserver observer) { |
||||
throw new NonDeterministicOperationException("Listening for new blocks is not deterministic"); |
||||
} |
||||
|
||||
@Override |
||||
public boolean removeObserver(final long observerId) { |
||||
throw new NonDeterministicOperationException("Listening for new blocks is not deterministic"); |
||||
} |
||||
|
||||
@Override |
||||
public long observeChainReorg(final ChainReorgObserver observer) { |
||||
throw new NonDeterministicOperationException("Listening for chain reorg is not deterministic"); |
||||
} |
||||
|
||||
@Override |
||||
public boolean removeChainReorgObserver(final long observerId) { |
||||
throw new NonDeterministicOperationException("Listening for chain reorg is not deterministic"); |
||||
} |
||||
|
||||
/** An exception thrown for methods not supported by the T8nBlockchain. */ |
||||
public static class NonDeterministicOperationException extends RuntimeException { |
||||
NonDeterministicOperationException(final String message) { |
||||
super(message); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings("unused") |
||||
public Comparator<BlockHeader> getBlockChoiceRule() { |
||||
return (a, b) -> { |
||||
throw new NonDeterministicOperationException( |
||||
"ReferenceTestBlockchain for VMTest Chains do not support fork choice rules"); |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public void setBlockChoiceRule(final Comparator<BlockHeader> blockChoiceRule) { |
||||
throw new UnsupportedOperationException("Not Used for Reference Tests"); |
||||
} |
||||
} |
Loading…
Reference in new issue