Skip to content

Commit 88a63f7

Browse files
✨ verifyBlock + toShortHeader (#1483) (#1493)
* ✨ verifyBlockHash + toShortHeader (#1483) Co-authored-by: clandestine.eth <[email protected]>
1 parent 208e4f3 commit 88a63f7

File tree

5 files changed

+371
-2
lines changed

5 files changed

+371
-2
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ jobs:
157157
- name: Install Foundry
158158
uses: foundry-rs/foundry-toolchain@v1
159159
with:
160-
version: nightly
160+
version: stable
161161
- name: Install Dependencies
162162
run: forge install
163163
- name: Run prep scripts and forge fmt

docs/utils/blockhashlib.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,40 @@ Library for accessing block hashes way beyond the 256-block limit.
99

1010
<!-- customintro:start --><!-- customintro:end -->
1111

12+
## Structs
13+
14+
### ShortHeader
15+
16+
```solidity
17+
struct ShortHeader {
18+
bytes32 parentHash;
19+
bytes32 stateRoot;
20+
bytes32 transactionsRoot;
21+
bytes32 receiptsRoot;
22+
bytes32[8] logsBloom;
23+
}
24+
```
25+
26+
Ethereum block header fields relevant to historical MPT proofs.
27+
28+
## Custom Errors
29+
30+
### BlockHashMismatch()
31+
32+
```solidity
33+
error BlockHashMismatch()
34+
```
35+
36+
The keccak256 of the RLP-encoded block header does not equal to the block hash.
37+
38+
### InvalidBlockHeaderEncoding()
39+
40+
```solidity
41+
error InvalidBlockHeaderEncoding()
42+
```
43+
44+
The block header is not properly RLP-encoded.
45+
1246
## Constants
1347

1448
### HISTORY_STORAGE_ADDRESS
@@ -34,4 +68,34 @@ function blockHash(uint256 blockNumber)
3468

3569
Retrieves the block hash for any historical block within the supported range.
3670
The function gracefully handles future blocks and blocks beyond the history window by returning zero,
37-
consistent with the EVM's native `BLOCKHASH` behavior.
71+
consistent with the EVM's native `BLOCKHASH` behavior.
72+
73+
### verifyBlock(bytes,uint256)
74+
75+
```solidity
76+
function verifyBlock(bytes calldata encodedHeader, uint256 blockNumber)
77+
internal
78+
view
79+
returns (bytes32 result)
80+
```
81+
82+
Reverts if `keccak256(encodedHeader) != blockHash(blockNumber)`,
83+
where `encodedHeader` is a RLP-encoded block header.
84+
Else, returns `blockHash(blockNumber)`.
85+
86+
### toShortHeader(bytes)
87+
88+
```solidity
89+
function toShortHeader(bytes calldata encodedHeader)
90+
internal
91+
pure
92+
returns (ShortHeader memory result)
93+
```
94+
95+
Retrieves the most relevant fields for MPT proofs from an RLP-encoded block header.
96+
Leading fields are always present and have fixed offsets and lengths.
97+
This function efficiently extracts the fields without full RLP decoding.
98+
For the specification of field order and lengths, please refer to
99+
prefix. 6 of the Ethereum Yellow Paper:
100+
(https://ethereum.github.io/yellowpaper/paper.pdf)
101+
and the Ethereum Wiki (https://epf.wiki/#/wiki/EL/RLP).

src/utils/BlockHashLib.sol

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ pragma solidity ^0.8.4;
55
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/BlockHashLib.sol)
66
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Blockhash.sol)
77
library BlockHashLib {
8+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9+
/* STRUCTS */
10+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11+
12+
/// @dev Ethereum block header fields relevant to historical MPT proofs.
13+
struct ShortHeader {
14+
bytes32 parentHash;
15+
bytes32 stateRoot;
16+
bytes32 transactionsRoot;
17+
bytes32 receiptsRoot;
18+
bytes32[8] logsBloom;
19+
}
20+
21+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
22+
/* CUSTOM ERRORS */
23+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
24+
25+
/// @dev The keccak256 of the RLP-encoded block header does not equal to the block hash.
26+
error BlockHashMismatch();
27+
28+
/// @dev The block header is not properly RLP-encoded.
29+
error InvalidBlockHeaderEncoding();
30+
831
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
932
/* CONSTANTS */
1033
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
@@ -35,4 +58,54 @@ library BlockHashLib {
3558
result := mload(0x00)
3659
}
3760
}
61+
62+
/// @dev Reverts if `keccak256(encodedHeader) != blockHash(blockNumber)`,
63+
/// where `encodedHeader` is a RLP-encoded block header.
64+
/// Else, returns `blockHash(blockNumber)`.
65+
function verifyBlock(bytes calldata encodedHeader, uint256 blockNumber)
66+
internal
67+
view
68+
returns (bytes32 result)
69+
{
70+
result = blockHash(blockNumber);
71+
/// @solidity memory-safe-assembly
72+
assembly {
73+
calldatacopy(mload(0x40), encodedHeader.offset, encodedHeader.length)
74+
if iszero(eq(result, keccak256(mload(0x40), encodedHeader.length))) {
75+
mstore(0x00, 0xe42b5e7e) // `BlockHashMismatch()`.
76+
revert(0x1c, 0x04)
77+
}
78+
}
79+
}
80+
81+
/// @dev Retrieves the most relevant fields for MPT proofs from an RLP-encoded block header.
82+
/// Leading fields are always present and have fixed offsets and lengths.
83+
/// This function efficiently extracts the fields without full RLP decoding.
84+
/// For the specification of field order and lengths, please refer to
85+
/// prefix. 6 of the Ethereum Yellow Paper:
86+
/// (https://ethereum.github.io/yellowpaper/paper.pdf)
87+
/// and the Ethereum Wiki (https://epf.wiki/#/wiki/EL/RLP).
88+
function toShortHeader(bytes calldata encodedHeader)
89+
internal
90+
pure
91+
returns (ShortHeader memory result)
92+
{
93+
/// @solidity memory-safe-assembly
94+
assembly {
95+
mstore(result, calldataload(add(4, encodedHeader.offset))) // `parentHash`.
96+
mstore(add(0x20, result), calldataload(add(91, encodedHeader.offset))) // `stateRoot`.
97+
mstore(add(0x40, result), calldataload(add(124, encodedHeader.offset))) // `transactionsRoot`.
98+
mstore(add(0x60, result), calldataload(add(157, encodedHeader.offset))) // `receiptsRoot`.
99+
calldatacopy(mload(add(0x80, result)), add(192, encodedHeader.offset), 0x100) // `logsBloom`.
100+
if iszero( // Just perform some minimal light bounds checking.
101+
and(
102+
gt(encodedHeader.length, 447), // `0x100 + 192 - 1`.
103+
eq(byte(0, calldataload(encodedHeader.offset)), 0xf9) // `0xff < len < 0x10000`.
104+
)
105+
) {
106+
mstore(0x00, 0x1a27c4e4) // `InvalidBlockHeaderEncoding()`.
107+
revert(0x1c, 0x04)
108+
}
109+
}
110+
}
38111
}

src/utils/g/BlockHashLib.sol

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.13;
3+
4+
// This file is auto-generated.
5+
6+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
7+
/* STRUCTS */
8+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
9+
10+
/// @dev Ethereum block header fields relevant to historical MPT proofs.
11+
struct ShortHeader {
12+
bytes32 parentHash;
13+
bytes32 stateRoot;
14+
bytes32 transactionsRoot;
15+
bytes32 receiptsRoot;
16+
bytes32[8] logsBloom;
17+
}
18+
19+
using BlockHashLib for ShortHeader global;
20+
21+
/// @notice Library for accessing block hashes way beyond the 256-block limit.
22+
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/g/BlockHashLib.sol)
23+
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Blockhash.sol)
24+
library BlockHashLib {
25+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
26+
/* CUSTOM ERRORS */
27+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
28+
29+
/// @dev The keccak256 of the RLP-encoded block header does not equal to the block hash.
30+
error BlockHashMismatch();
31+
32+
/// @dev The block header is not properly RLP-encoded.
33+
error InvalidBlockHeaderEncoding();
34+
35+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
36+
/* CONSTANTS */
37+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
38+
39+
/// @dev Address of the EIP-2935 history storage contract.
40+
/// See: https://eips.ethereum.org/EIPS/eip-2935
41+
address internal constant HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53a10cb7A02335B175320002935;
42+
43+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
44+
/* OPERATIONS */
45+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
46+
47+
/// @dev Retrieves the block hash for any historical block within the supported range.
48+
/// The function gracefully handles future blocks and blocks beyond the history window by returning zero,
49+
/// consistent with the EVM's native `BLOCKHASH` behavior.
50+
function blockHash(uint256 blockNumber) internal view returns (bytes32 result) {
51+
unchecked {
52+
// If `blockNumber + 256` overflows:
53+
// - Typical chain height (`block.number > 255`) -> `staticcall` -> 0.
54+
// - Very early chain (`block.number <= 255`) -> `blockhash` -> 0.
55+
if (block.number <= blockNumber + 256) return blockhash(blockNumber);
56+
}
57+
/// @solidity memory-safe-assembly
58+
assembly {
59+
mstore(0x20, blockNumber)
60+
mstore(0x00, 0)
61+
pop(staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0x20, 0x20, 0x00, 0x20))
62+
result := mload(0x00)
63+
}
64+
}
65+
66+
/// @dev Reverts if `keccak256(encodedHeader) != blockHash(blockNumber)`,
67+
/// where `encodedHeader` is a RLP-encoded block header.
68+
/// Else, returns `blockHash(blockNumber)`.
69+
function verifyBlock(bytes calldata encodedHeader, uint256 blockNumber)
70+
internal
71+
view
72+
returns (bytes32 result)
73+
{
74+
result = blockHash(blockNumber);
75+
/// @solidity memory-safe-assembly
76+
assembly {
77+
calldatacopy(mload(0x40), encodedHeader.offset, encodedHeader.length)
78+
if iszero(eq(result, keccak256(mload(0x40), encodedHeader.length))) {
79+
mstore(0x00, 0xe42b5e7e) // `BlockHashMismatch()`.
80+
revert(0x1c, 0x04)
81+
}
82+
}
83+
}
84+
85+
/// @dev Retrieves the most relevant fields for MPT proofs from an RLP-encoded block header.
86+
/// Leading fields are always present and have fixed offsets and lengths.
87+
/// This function efficiently extracts the fields without full RLP decoding.
88+
/// For the specification of field order and lengths, please refer to
89+
/// prefix. 6 of the Ethereum Yellow Paper:
90+
/// (https://ethereum.github.io/yellowpaper/paper.pdf)
91+
/// and the Ethereum Wiki (https://epf.wiki/#/wiki/EL/RLP).
92+
function toShortHeader(bytes calldata encodedHeader)
93+
internal
94+
pure
95+
returns (ShortHeader memory result)
96+
{
97+
/// @solidity memory-safe-assembly
98+
assembly {
99+
mstore(result, calldataload(add(4, encodedHeader.offset))) // `parentHash`.
100+
mstore(add(0x20, result), calldataload(add(91, encodedHeader.offset))) // `stateRoot`.
101+
mstore(add(0x40, result), calldataload(add(124, encodedHeader.offset))) // `transactionsRoot`.
102+
mstore(add(0x60, result), calldataload(add(157, encodedHeader.offset))) // `receiptsRoot`.
103+
calldatacopy(mload(add(0x80, result)), add(192, encodedHeader.offset), 0x100) // `logsBloom`.
104+
if iszero( // Just perform some minimal light bounds checking.
105+
and(
106+
gt(encodedHeader.length, 447), // `0x100 + 192 - 1`.
107+
eq(byte(0, calldataload(encodedHeader.offset)), 0xf9) // `0xff < len < 0x10000`.
108+
)
109+
) {
110+
mstore(0x00, 0x1a27c4e4) // `InvalidBlockHeaderEncoding()`.
111+
revert(0x1c, 0x04)
112+
}
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)