Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
cdb6bd7
feat: trace all previous blockhashes
letypequividelespoubelles Sep 29, 2025
3291986
feat: some stuff
letypequividelespoubelles Sep 30, 2025
a1e59dc
Merge branch 'arith-dev' into 2348-add-previous-blockhashes-to-the-co…
letypequividelespoubelles Oct 1, 2025
1fe4014
rebase
letypequividelespoubelles Oct 1, 2025
a65dbec
rm the try catch, if else is better + fix when dealing with first blo…
letypequividelespoubelles Oct 1, 2025
f7c987f
fix line counting
letypequividelespoubelles Oct 1, 2025
374fe0d
spotless
letypequividelespoubelles Oct 1, 2025
12e274c
update corset
letypequividelespoubelles Oct 1, 2025
d3283b9
ras
letypequividelespoubelles Oct 1, 2025
55556e2
rebase
letypequividelespoubelles Oct 1, 2025
1fde88d
ras
letypequividelespoubelles Oct 1, 2025
53257e6
ras rebase
letypequividelespoubelles Oct 1, 2025
c964177
add blockhash line count at begining of the conflation in zktracer
letypequividelespoubelles Oct 1, 2025
284f8b7
test: move blockhash test to besu node tests
letypequividelespoubelles Oct 1, 2025
afc5d85
fix: wrong cast to short
letypequividelespoubelles Oct 1, 2025
01e6802
rebase
letypequividelespoubelles Oct 3, 2025
b07cbc9
ras rebase
letypequividelespoubelles Oct 7, 2025
860d03d
ras
letypequividelespoubelles Oct 7, 2025
b8bbcf5
default constructor wo blockchainservices
letypequividelespoubelles Oct 7, 2025
975d9fb
redo from scratch cleaner way
letypequividelespoubelles Oct 9, 2025
17a0055
make it cleaner
letypequividelespoubelles Oct 9, 2025
e649367
add last block too
letypequividelespoubelles Oct 9, 2025
6d52a97
Merge branch 'arith-dev' into 2348-add-previous-blockhashes-to-the-co…
letypequividelespoubelles Oct 9, 2025
12499a6
fix: trm leading zeros ...
letypequividelespoubelles Oct 9, 2025
5e37279
ras stupid stuff
letypequividelespoubelles Oct 10, 2025
69f8899
some more stupid stuff
letypequividelespoubelles Oct 10, 2025
db32703
fix: line count before end conflation
letypequividelespoubelles Oct 13, 2025
fa0abf5
ras rebase
letypequividelespoubelles Oct 13, 2025
f7aa5cf
amelie's comment
letypequividelespoubelles Oct 13, 2025
7accb81
feat: store last block of the conflation blockhash
letypequividelespoubelles Oct 14, 2025
aebd4ab
feat: modify the replay mechanism to dd the blockhash history
letypequividelespoubelles Oct 15, 2025
e6ce843
Merge branch 'arith-dev' into 2348-add-previous-blockhashes-to-the-co…
letypequividelespoubelles Oct 15, 2025
ffbc23e
update consraints
letypequividelespoubelles Oct 15, 2025
3bf9585
trigger blockhash from eip2935 section
letypequividelespoubelles Oct 15, 2025
5a592bb
ras
letypequividelespoubelles Oct 15, 2025
0046e4c
fix: don't call blockhash if exceptional
letypequividelespoubelles Oct 15, 2025
c1068c8
retrieve historical blockhaseh for replay tests
letypequividelespoubelles Oct 15, 2025
27b52fe
test: skip tests where we can't extract data from gson
letypequividelespoubelles Oct 15, 2025
b973e31
tmp: gradle properties
letypequividelespoubelles Oct 15, 2025
c34f97d
add tests for sepolia replay preparation
letypequividelespoubelles Oct 16, 2025
91cf927
Merge branch 'arith-dev' into 2348-add-previous-blockhashes-to-the-co…
letypequividelespoubelles Oct 16, 2025
3bc11d6
add system tx for block capturer
letypequividelespoubelles Oct 16, 2025
4746eca
ras
letypequividelespoubelles Oct 16, 2025
27addb1
Encode/decode parentBeaconBlockRoot as hex string
DavePearce Oct 16, 2025
fa1b3bd
don't load clique headers from Shanghai onwards
DavePearce Oct 16, 2025
42a2230
restore BlockHashSnapshot
DavePearce Oct 17, 2025
5c64388
minor fixes
DavePearce Oct 17, 2025
febd637
add replay tests and little debug
letypequividelespoubelles Oct 17, 2025
c6df212
ras
letypequividelespoubelles Oct 17, 2025
f06c44b
rebase
letypequividelespoubelles Oct 17, 2025
077effe
fix Reaper and ConflationSnapshot
DavePearce Oct 17, 2025
b0dc1c5
add replay tests from PRAGUE
DavePearce Oct 17, 2025
599cee0
rebase
letypequividelespoubelles Oct 23, 2025
62e8170
rebase
letypequividelespoubelles Oct 24, 2025
077d742
nothing important
letypequividelespoubelles Oct 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package net.consensys.linea.blockcapture;

import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gson.Gson;
Expand All @@ -40,10 +41,6 @@
import org.hyperledger.besu.plugin.data.BlockHeader;

public class BlockCapturer implements ConflationAwareOperationTracer {
public static final int MAX_RELATIVE_BLOCK = 256;

private static final int MAX_BLOCK_ARG_SIZE = 8;

/**
* The {@link Reaper} will collect all the data that will need to be mimicked to replay the block.
*/
Expand All @@ -60,12 +57,14 @@ public class BlockCapturer implements ConflationAwareOperationTracer {

/**
* Construct a BlockCapturer instance for a specific fork. This is necessary to ensure opcodes are
* loaded before hand.
* loaded beforehand.
*
* @param fork
*/
public BlockCapturer(Fork fork) {
this.opcodes = OpCodes.load(fork);
public BlockCapturer(Fork fork, Map<Long, Hash> historicalBlockHashes) {
opcodes = OpCodes.load(fork);
for (Map.Entry<Long, Hash> entry : historicalBlockHashes.entrySet())
reaper.touchBlockHash(entry.getKey(), entry.getValue());
}

/**
Expand All @@ -89,12 +88,19 @@ public void traceStartBlock(
BlockHeader blockHeader,
BlockBody blockBody,
final Address miningBeneficiary) {
this.reaper.enterBlock(blockHeader, blockBody, miningBeneficiary);
reaper.enterBlock(blockHeader, blockBody, miningBeneficiary);
}

// used to record the block hash of the last block in the conflation (previous one are already
// recorded in the constructor)
@Override
public void traceEndBlock(final BlockHeader blockHeader, final BlockBody blockBody) {
reaper.touchBlockHash(blockHeader.getNumber(), blockHeader.getBlockHash());
}

@Override
public void tracePrepareTransaction(WorldView worldView, Transaction transaction) {
this.reaper.prepareTransaction(transaction);
reaper.prepareTransaction(transaction);
}

@Override
Expand All @@ -107,7 +113,7 @@ public void traceEndTransaction(
long gasUsed,
Set<Address> selfDestructs,
long timeNs) {
this.reaper.exitTransaction(world, status, output, logs, gasUsed, selfDestructs);
reaper.exitTransaction(world, status, output, logs, gasUsed, selfDestructs);
}

/**
Expand All @@ -125,7 +131,7 @@ public void tracePreExecution(MessageFrame frame) {
case EXTCODESIZE, EXTCODECOPY, EXTCODEHASH -> {
if (frame.stackSize() > 0) {
final Address target = Words.toAddress(frame.getStackItem(0));
this.reaper.touchAddress(target);
reaper.touchAddress(target);
}
}

Expand All @@ -135,7 +141,7 @@ public void tracePreExecution(MessageFrame frame) {
final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress());
final Address address = account.getAddress();
final UInt256 key = UInt256.fromBytes(frame.getStackItem(0));
this.reaper.touchStorage(address, key);
reaper.touchStorage(address, key);
}
}

Expand All @@ -145,65 +151,45 @@ public void tracePreExecution(MessageFrame frame) {
final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress());
final Address address = account.getAddress();
final UInt256 key = UInt256.fromBytes(frame.getStackItem(0));
this.reaper.touchStorage(address, key);
reaper.touchStorage(address, key);
}
}

// These access contracts potentially existing before the conflation played out.
case CALL, CALLCODE, DELEGATECALL, STATICCALL -> {
if (frame.stackSize() > 1) {
final Address target = Words.toAddress(frame.getStackItem(1));
this.reaper.touchAddress(target);
reaper.touchAddress(target);
}
}

case BALANCE -> {
if (frame.stackSize() > 0) {
final Address target = Words.toAddress(frame.getStackItem(0));
this.reaper.touchAddress(target);
reaper.touchAddress(target);
}
}

// Failure condition if created address already exists
case CREATE, CREATE2 -> {
if (frame.stackSize() > 0) {
final Address target = AddressUtils.getDeploymentAddress(frame, opCode);
this.reaper.touchAddress(target);
reaper.touchAddress(target);
}
}

// Funds of the selfdestruct account are sent to the target account
case SELFDESTRUCT -> {
if (frame.stackSize() > 0) {
final Address target = Words.toAddress(frame.getStackItem(0));
this.reaper.touchAddress(target);
}
}

case BLOCKHASH -> {
if (frame.stackSize() > 0) {
// Determine current block number
final long currentBlockNumber = frame.getBlockValues().getNumber();
final Bytes arg = frame.getStackItem(0).trimLeadingZeros();
// Check arguments fits within 8 bytes
if (arg.size() <= 8) {
// Determine block number requested
final long blockNumber = arg.toLong();
// Sanity check block within last 256 blocks.
if (blockNumber < currentBlockNumber && (currentBlockNumber - blockNumber) <= 256) {
// Use enclosing frame to determine hash
Hash blockHash = frame.getBlockHashLookup().apply(frame, blockNumber);
// Record it was seen
this.reaper.touchBlockHash(blockNumber, blockHash);
}
}
reaper.touchAddress(target);
}
}
}
}

public String toJson() {
Gson gson = new Gson();
return gson.toJson(this.reaper.collapse(this.worldUpdater));
return gson.toJson(reaper.collapse(worldUpdater));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@

package net.consensys.linea.blockcapture.reapers;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static net.consensys.linea.zktracer.Trace.HISTORY_BUFFER_LENGTH;
import static net.consensys.linea.zktracer.Trace.HISTORY_SERVE_WINDOW;
import static net.consensys.linea.zktracer.module.hub.section.systemTransaction.EIP2935HistoricalHash.EIP2935_HISTORY_STORAGE_ADDRESS;
import static net.consensys.linea.zktracer.module.hub.section.systemTransaction.EIP4788BeaconBlockRootSection.EIP4788_BEACONROOT_ADDRESS;

import java.util.*;

import lombok.extern.slf4j.Slf4j;
import net.consensys.linea.blockcapture.snapshots.AccountSnapshot;
import net.consensys.linea.blockcapture.snapshots.BlockHashSnapshot;
import net.consensys.linea.blockcapture.snapshots.BlockSnapshot;
import net.consensys.linea.blockcapture.snapshots.ConflationSnapshot;
import net.consensys.linea.blockcapture.snapshots.StorageSnapshot;
Expand All @@ -44,6 +48,7 @@
* <p>This data can than be collapsed into a “replay” ({@link ConflationSnapshot}), i.e. the minimal
* required information to replay a conflation as if it were executed on the blockchain.
*/
@Slf4j
public class Reaper {
/** Collect storage locations read / written by the entire conflation */
private final StorageReaper conflationStorage = new StorageReaper();
Expand All @@ -52,7 +57,7 @@ public class Reaper {
private final AddressReaper conflationAddresses = new AddressReaper();

/** Collection all block hashes read during the conflation * */
private final BlockHashReaper conflationHashes = new BlockHashReaper();
private final Map<Long, Hash> conflationHashes = new HashMap<>();

/** Collect the blocks within a conflation */
private final List<BlockSnapshot> blocks = new ArrayList<>();
Expand All @@ -75,6 +80,38 @@ public void enterBlock(
BlockSnapshot.of((org.hyperledger.besu.ethereum.core.BlockHeader) header, body));
this.conflationAddresses.touch(miningBeneficiary);
txIndex = 0; // reset
touchedBySystemTransactions(header);
}

private void touchedBySystemTransactions(BlockHeader header) {
// EIP 4788 (CANCUN):
try {
conflationAddresses.touch(EIP4788_BEACONROOT_ADDRESS);
final UInt256 timestamp = UInt256.valueOf(header.getTimestamp());
final UInt256 keyTimestamp = timestamp.mod(HISTORY_BUFFER_LENGTH);
conflationStorage.touch(EIP4788_BEACONROOT_ADDRESS, timestamp);
conflationStorage.touch(EIP4788_BEACONROOT_ADDRESS, keyTimestamp);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keys that are touched by 4788 system transactions are

brKey := timestamp mod 8191
tsKey := brKey + 8191

Am I misunderstanding here ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, you're right

} catch (Exception e) {
log.warn(
"Failed to retrieve EIP4788 infos for block {}, exception caught is: {}",
header.getNumber(),
e.getMessage());
}

// EIP 2935 (PRAGUE)
try {
conflationAddresses.touch(EIP2935_HISTORY_STORAGE_ADDRESS);
final long blockNumber = header.getNumber();
final long previousBlockNumber = blockNumber == 0 ? 0 : blockNumber - 1;
final UInt256 previousBlockNumberMod8191 =
UInt256.valueOf(previousBlockNumber % HISTORY_SERVE_WINDOW);
conflationStorage.touch(EIP2935_HISTORY_STORAGE_ADDRESS, previousBlockNumberMod8191);
} catch (Exception e) {
log.warn(
"Failed to retrieve EIP2935 infos for block {}, exception caught is: {}",
header.getNumber(),
e.getMessage());
}
}

public void prepareTransaction(Transaction tx) {
Expand All @@ -96,7 +133,7 @@ public void exitTransaction(
TransactionSnapshot txSnapshot = blocks.getLast().txs().get(txIndex);
// Convert logs into hex strings
List<String> logStrings = logs.stream().map(l -> l.getData().toHexString()).toList();
// Convert destructed account addresses into into hex strings
// Convert destructed account addresses into hex strings
List<String> destructStrings = selfDestructs.stream().map(Address::toHexString).toList();
// Collapse accounts
final List<AccountSnapshot> accounts = this.txAddresses.collapse(world);
Expand Down Expand Up @@ -125,8 +162,16 @@ public void touchStorage(final Address address, final UInt256 key) {
}

public void touchBlockHash(final long blockNumber, Hash blockHash) {
this.conflationHashes.touch(blockNumber, blockHash);
// No need to tx local hashes, since they are a global concept.
if (!conflationHashes.containsKey(blockNumber) || conflationHashes.get(blockNumber).isEmpty()) {
conflationHashes.put(blockNumber, blockHash);
} else {
checkArgument(
conflationHashes.get(blockNumber).equals(blockHash),
"Block hash mismatch for block number %s: existing %s, new %s",
blockNumber,
conflationHashes.get(blockNumber),
blockHash);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Null Handling Error in Hash Retrieval

The touchBlockHash method calls .isEmpty() on the Hash object retrieved from conflationHashes.get(blockNumber). The Hash class does not have an isEmpty() method, resulting in a compilation error. If the block number is not present in the map, get() returns null, which would cause a NullPointerException.

Fix in Cursor Fix in Web

}

/**
Expand All @@ -139,12 +184,10 @@ public void touchBlockHash(final long blockNumber, Hash blockHash) {
*/
public ConflationSnapshot collapse(final WorldUpdater world) {
// Collapse accounts
final List<AccountSnapshot> accounts = this.conflationAddresses.collapse(world);
final List<AccountSnapshot> accounts = conflationAddresses.collapse(world);
// Collapse storage
final List<StorageSnapshot> storage = conflationStorage.collapse(world);
// Collapse block hashes
final List<BlockHashSnapshot> hashes = conflationHashes.collapse();
// Done
return new ConflationSnapshot(this.blocks, accounts, storage, hashes);
return ConflationSnapshot.from(blocks, accounts, storage, conflationHashes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public record BlockHeaderSnapshot(
String mixHashOrPrevRandao,
long nonce,
Optional<String> baseFee,
Bytes32 parentBeaconBlockRoot) {
Optional<String> parentBeaconBlockRoot) {
public static BlockHeaderSnapshot from(BlockHeader header) {
return new BlockHeaderSnapshot(
header.getParentHash().toHexString(),
Expand All @@ -65,7 +65,7 @@ public static BlockHeaderSnapshot from(BlockHeader header) {
header.getMixHashOrPrevRandao().toHexString(),
header.getNonce(),
header.getBaseFee().map(Quantity::toHexString),
header.getParentBeaconBlockRoot().orElse(null));
header.getParentBeaconBlockRoot().map(Bytes::toHexString));
}

public BlockHeader toBlockHeader() {
Expand All @@ -87,11 +87,15 @@ public BlockHeader toBlockHeader() {
.mixHash(Hash.fromHexString(this.mixHashOrPrevRandao))
.prevRandao(Bytes32.fromHexString(this.mixHashOrPrevRandao))
.nonce(this.nonce)
.blockHeaderFunctions(new CliqueBlockHeaderFunctions())
.parentBeaconBlockRoot(parentBeaconBlockRoot);
.blockHeaderFunctions(new CliqueBlockHeaderFunctions());

this.baseFee.ifPresent(baseFee -> builder.baseFee(Wei.fromHexString(baseFee)));

// Following null check appears to be necessary for older replays.
if (this.parentBeaconBlockRoot != null) {
this.parentBeaconBlockRoot.ifPresent(
root -> builder.parentBeaconBlockRoot(Bytes32.fromHexString(root)));
}
//
return builder.buildBlockHeader();
}
}
Loading
Loading