Skip to content

Commit 9410276

Browse files
committed
T
1 parent 9188019 commit 9410276

3 files changed

Lines changed: 261 additions & 72 deletions

File tree

src/utils/BlockHashLib.sol

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.4;
33

4-
/// @dev Ethereum block header fields relevant to historical MPT proofs.
5-
struct ShortHeader {
6-
bytes32 parentHash;
7-
bytes32 stateRoot;
8-
bytes32 transactionsRoot;
9-
bytes32 receiptsRoot;
10-
bytes32[8] logsBloom;
11-
}
12-
134
/// @notice Library for accessing block hashes way beyond the 256-block limit.
145
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/BlockHashLib.sol)
156
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Blockhash.sol)
167
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+
1721
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
1822
/* CUSTOM ERRORS */
1923
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
2024

21-
/// @dev Invalid block hash.
22-
error InvalidBlockHeader();
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();
2330

2431
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
2532
/* CONSTANTS */
@@ -52,45 +59,54 @@ library BlockHashLib {
5259
}
5360
}
5461

55-
/// @dev Returns whether the hash of a provided RLP-encoded block `header` equals the block hash at `blockNumber`.
56-
function verifyBlockHash(bytes calldata header, uint256 blockNumber)
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)
5766
internal
5867
view
5968
returns (bytes32 result)
6069
{
6170
result = blockHash(blockNumber);
6271
/// @solidity memory-safe-assembly
6372
assembly {
64-
calldatacopy(mload(0x40), header.offset, header.length)
65-
if iszero(eq(result, keccak256(mload(0x40), header.length))) {
66-
mstore(0x00, 0x464db2f8) // InvalidBlockHeader()
73+
calldatacopy(mload(0x40), encodedHeader.offset, encodedHeader.length)
74+
if iszero(eq(result, keccak256(mload(0x40), encodedHeader.length))) {
75+
mstore(0x00, 0xe42b5e7e) // `BlockHashMismatch()`.
6776
revert(0x1c, 0x04)
6877
}
6978
}
7079
}
7180

72-
/// @dev Retrieves the most relevant fields for MPT proofs from an RLP-encoded block `header`.
81+
/// @dev Retrieves the most relevant fields for MPT proofs from an RLP-encoded block header.
7382
/// Leading fields are always present and have fixed offsets and lengths.
74-
/// This function allows efficient extraction of these fields from calldata without full RLP decoding.
75-
/// For the specification of field order and lengths, please refer to prefix. 6 of the Ethereum Yellow Paper:
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:
7686
/// (https://ethereum.github.io/yellowpaper/paper.pdf)
7787
/// and the Ethereum Wiki (https://epf.wiki/#/wiki/EL/RLP).
78-
function toShortHeader(bytes calldata header)
88+
function toShortHeader(bytes calldata encodedHeader)
7989
internal
8090
pure
8191
returns (ShortHeader memory result)
8292
{
8393
/// @solidity memory-safe-assembly
8494
assembly {
85-
let m := mload(0x40)
86-
let o := add(header.offset, sub(byte(0, calldataload(header.offset)), 0xF6))
87-
mstore(result, calldataload(add(1, o))) // parentHash
88-
mstore(add(0x20, result), calldataload(add(88, o))) // stateRoot
89-
mstore(add(0x40, result), calldataload(add(121, o))) // transactionsRoot
90-
mstore(add(0x60, result), calldataload(add(154, o))) // receiptsRoot
91-
mstore(add(0x80, result), m) // logsBloom
92-
calldatacopy(m, add(189, o), 0x100)
93-
mstore(0x40, add(0x100, m)) // Allocate the memory.
95+
// Just perform some minimal light bounds checking.
96+
if iszero(
97+
and(
98+
gt(encodedHeader.length, 447), // `0x100 + 192 - 1`.
99+
eq(byte(0, calldataload(encodedHeader.offset)), 0xf9) // `0xff < len < 0x10000`.
100+
)
101+
) {
102+
mstore(0x00, 0x1a27c4e4) // `InvalidBlockHeaderEncoding()`.
103+
revert(0x1c, 0x04)
104+
}
105+
mstore(result, calldataload(add(4, encodedHeader.offset))) // `parentHash`.
106+
mstore(add(0x20, result), calldataload(add(91, encodedHeader.offset))) // `stateRoot`.
107+
mstore(add(0x40, result), calldataload(add(124, encodedHeader.offset))) // `transactionsRoot`.
108+
mstore(add(0x60, result), calldataload(add(157, encodedHeader.offset))) // `receiptsRoot`.
109+
calldatacopy(mload(add(0x80, result)), add(192, encodedHeader.offset), 0x100) // `logsBloom`.
94110
}
95111
}
96112
}

src/utils/g/BlockHashLib.sol

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
// Just perform some minimal light bounds checking.
100+
if iszero(
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+
mstore(result, calldataload(add(4, encodedHeader.offset))) // `parentHash`.
110+
mstore(add(0x20, result), calldataload(add(91, encodedHeader.offset))) // `stateRoot`.
111+
mstore(add(0x40, result), calldataload(add(124, encodedHeader.offset))) // `transactionsRoot`.
112+
mstore(add(0x60, result), calldataload(add(157, encodedHeader.offset))) // `receiptsRoot`.
113+
calldatacopy(mload(add(0x80, result)), add(192, encodedHeader.offset), 0x100) // `logsBloom`.
114+
}
115+
}
116+
}

test/BlockHashLib.t.sol

Lines changed: 100 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,30 @@
22
pragma solidity ^0.8.4;
33

44
import "./utils/SoladyTest.sol";
5-
import {BlockHashLib, ShortHeader} from "../src/utils/BlockHashLib.sol";
5+
import {BlockHashLib} from "../src/utils/BlockHashLib.sol";
6+
import {LibRLP} from "../src/utils/LibRLP.sol";
67

78
contract BlockHashLibTest is SoladyTest {
9+
using LibRLP for *;
10+
11+
struct BlockHeader {
12+
bytes32 parentHash;
13+
bytes32 ommersHash;
14+
bytes20 beneficiary;
15+
bytes32 stateRoot;
16+
bytes32 transactionsRoot;
17+
bytes32 receiptsRoot;
18+
bytes32[8] logsBloom;
19+
bytes32 difficultyOrPrevrandao;
20+
uint256 number;
21+
uint256 gasLimit;
22+
uint256 gasUsed;
23+
uint256 timestamp;
24+
bytes extraData;
25+
bytes32 mixHash;
26+
bytes8 nonce;
27+
}
28+
829
uint256 internal startingBlock;
930

1031
address internal constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE;
@@ -67,54 +88,90 @@ contract BlockHashLibTest is SoladyTest {
6788
}
6889
}
6990

70-
function beforeTestSetup(bytes4 selector) public pure returns (bytes[] memory cd) {
71-
if (selector == this.testToShortHeader.selector) {
72-
cd = new bytes[](1);
73-
cd[0] = abi.encodeWithSelector(this.checkToShortHeader.selector, _ETH_BLOCK_23270177);
91+
function testVerifyBlock() public {
92+
vm.roll(23270177 + 1);
93+
vm.setBlockhash(23270177, _ETH_BLOCK_HASH_23270177);
94+
assertEq(this.verifyBlock(_ETH_BLOCK_23270177, 23270177), _ETH_BLOCK_HASH_23270177);
95+
}
96+
97+
function verifyBlock(bytes calldata blockHeader, uint256 blockNumber)
98+
public
99+
view
100+
returns (bytes32)
101+
{
102+
return BlockHashLib.verifyBlock(blockHeader, blockNumber);
103+
}
104+
105+
function _randomBlockHeader() internal returns (BlockHeader memory b, bytes memory encoded) {
106+
if (_randomChance(2)) {
107+
b.parentHash = bytes32(_random());
108+
b.ommersHash = bytes32(_random());
109+
b.beneficiary = bytes20(bytes32(_random()));
110+
b.stateRoot = bytes32(_random());
111+
b.transactionsRoot = bytes32(_random());
112+
b.receiptsRoot = bytes32(_random());
74113
}
75-
if (selector == this.testVerifyBlockHash.selector) {
76-
cd = new bytes[](1);
77-
cd[0] = abi.encodeWithSelector(this.checkVerifyBlockHash.selector, _ETH_BLOCK_23270177);
114+
if (_randomChance(2)) {
115+
for (uint256 i; i < 8; ++i) {
116+
b.logsBloom[i] = bytes32(_random());
117+
}
118+
}
119+
if (_randomChance(2)) {
120+
b.difficultyOrPrevrandao = bytes32(_random());
121+
b.number = _random();
122+
b.gasLimit = _random();
123+
b.gasUsed = _random();
124+
b.timestamp = _random();
125+
b.extraData = _truncateBytes(_randomBytes(), 32);
126+
b.mixHash = bytes32(_random());
127+
b.nonce = bytes8(bytes32(_random()));
78128
}
79-
}
80129

81-
function checkToShortHeader(bytes calldata h) public {
82-
ShortHeader memory expected = ShortHeader({
83-
parentHash: 0x1581f4448b16694d5a728161cd65f8c80b88f5352a6f5bd2d2315b970582958d,
84-
stateRoot: 0x10d2afa5dabcf2dbfe3aa82b758427938e07880bd6fef3c82c404d0dd7c3f0f3,
85-
transactionsRoot: 0xf81230c715a462c827898bf2e337982907a7af90e5be20f911785bda05dab93c,
86-
receiptsRoot: 0x740f11bc75cf25e40d78d892d2e03083eaa573e5b4c26913fcc1b833db854c94,
87-
logsBloom: [
88-
bytes32(0x85f734fb06ea8fe377abbcb2e27f9ac99751ba817dc327327db101fd76f964ed), // lol
89-
0x0b7ca161f148fc165b9e5b575dc7473f17f4b8ebbf4a7b02b3e1e642197f27b2,
90-
0xaf54680834449abaf833619ac7d18afb50b19d5f6944dca0dc952edfdd983757,
91-
0x3783c339ee6a36353ce6e536eaaf29fcd569c426091d4e24568dc353347f98c7,
92-
0x4fb6f8c91d68d358467c437563f66566377fe6c3f9e8301dbeb5fc7e7adee7a8,
93-
0x5ef5f8fa905cedbaf26601e21ba91646cac4034601e51d889d49739ee6990943,
94-
0xa6a41927660f68e1f50b9f9209ee29551a7dae478d88e0547eefc83334ea770b,
95-
0xb6fbac620fc47479c2c59389622bf32f55e36a75e56a5fc47c38bf8ef211fc0e
96-
]
97-
});
98-
99-
ShortHeader memory actual = BlockHashLib.toShortHeader(h);
100-
assertEq(actual.parentHash, expected.parentHash, "parentHash");
101-
assertEq(actual.stateRoot, expected.stateRoot, "stateRoot");
102-
assertEq(actual.transactionsRoot, expected.transactionsRoot, "transactionsRoot");
103-
assertEq(actual.receiptsRoot, expected.receiptsRoot, "receiptsRoot");
104-
assertEq(
105-
keccak256(abi.encodePacked(actual.logsBloom)),
106-
keccak256(abi.encodePacked(expected.logsBloom)),
107-
"logsBloom"
108-
);
130+
LibRLP.List memory l;
131+
l.p(abi.encodePacked(b.parentHash));
132+
l.p(abi.encodePacked(b.ommersHash));
133+
l.p(abi.encodePacked(b.beneficiary));
134+
l.p(abi.encodePacked(b.stateRoot));
135+
l.p(abi.encodePacked(b.transactionsRoot));
136+
l.p(abi.encodePacked(b.receiptsRoot));
137+
l.p(abi.encodePacked(b.logsBloom));
138+
l.p(abi.encodePacked(b.difficultyOrPrevrandao));
139+
l.p(b.number);
140+
l.p(b.gasLimit);
141+
l.p(b.gasUsed);
142+
l.p(b.timestamp);
143+
l.p(b.extraData);
144+
l.p(abi.encodePacked(b.mixHash));
145+
l.p(abi.encodePacked(b.nonce));
146+
encoded = l.encode();
109147
}
110148

111-
function checkVerifyBlockHash(bytes calldata h) public {
112-
vm.roll(23270177 + 1);
113-
vm.setBlockhash(23270177, _ETH_BLOCK_HASH_23270177);
114-
assertEq(BlockHashLib.verifyBlockHash(h, 23270177), _ETH_BLOCK_HASH_23270177);
149+
function testToShortHeader(bytes32) public {
150+
(BlockHeader memory b, bytes memory encoded) = _randomBlockHeader();
151+
BlockHashLib.ShortHeader memory s =
152+
this.toShortHeader(_truncateBytes(_randomBytes(), 128), encoded);
153+
assertEq(s.parentHash, b.parentHash);
154+
assertEq(s.stateRoot, b.stateRoot);
155+
assertEq(s.transactionsRoot, b.transactionsRoot);
156+
assertEq(s.receiptsRoot, b.receiptsRoot);
157+
for (uint256 i; i < 8; ++i) {
158+
assertEq(s.logsBloom[i], b.logsBloom[i]);
159+
}
115160
}
116161

117-
function testToShortHeader() public view {}
162+
function toShortHeader(bytes calldata, bytes calldata encodedHeader)
163+
public
164+
view
165+
returns (BlockHashLib.ShortHeader memory result)
166+
{
167+
_misalignFreeMemoryPointer();
168+
_brutalizeMemory();
169+
result = BlockHashLib.toShortHeader(encodedHeader);
170+
_checkMemory();
171+
}
118172

119-
function testVerifyBlockHash() public view {}
173+
function testRandomBlockHeader(bytes32) public {
174+
(, bytes memory encoded) = _randomBlockHeader();
175+
assertEq(uint8(bytes1(encoded[0])), 0xf9);
176+
}
120177
}

0 commit comments

Comments
 (0)