2

I've tried endless combinations and failed to reproduce the block.hash by hashing the block.header for post-merge blocks. Could someone in simple terms explain what are the exact current elements in the block header needed to calculate it's block hash as of May 2024? This information should be easy to access, but it changes with all major updates that effect the block header.

Note: '0x' and 0n produce the same hash, so I just stick with the native block.xyz for values with numbers.

I have both solutions for blocks 1 to 15.5M. Summary of code below: block header starts with 15 elements, ending with a nonce. After London, we need block.baseFeePerGas. But after the merge, neither techniques work. I've tried multiple combinations using withdrawalsRoot, blobGasUsed, excessBlobGas, parentBeaconBlockRoot, and requestsRoot.

Some references:

const RLP = require('rlp');
const { ethers } = require("ethers");
const { Web3 } = require('web3');
require("dotenv").config({ path: '../.env' });

const web3 = new Web3(process.env.RPC_URL_MAINNET);

// Works from block 1 to 12,964,999 (London EIP-1559). Does not work on block 12,965,000 and beyond.
async function verifyHashOldBlocks() {
    // 1. Get the full block
    const block = await web3.eth.getBlock(400000);
    console.log(block.hash); // '0x5d15649e25d8f3e2c0374946078539d200710afc977cdfc6a977bd23f20fa8e8'

    // 2. Make array with only block header items. Any numbers are converted to hex.
    const blockHeader = [
        block.parentHash, // '0x1e77d8f1267348b516ebc4f4da1e2aa59f85f0cbd853949500ffac8bfc38ba14'
        block.sha3Uncles, // '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'
        block.miner, // '0x2a65aca4d5fc5b5c859090a6c34d164135398226'
        block.stateRoot, // '0x0b5e4386680f43c224c5c037efc0b645c8e1c3f6b30da0eec07272b4e6f8cd89'
        block.transactionsRoot, // '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'
        block.receiptsRoot, // '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'
        block.logsBloom, // '0x000...000'
        block.difficulty, // 6022643743806n --> '0x57a418a7c3e'
        block.number, // 400000n --> '0x61a80'
        block.gasLimit, // 3141592n --> '0x2fefd8'
        block.gasUsed, // 0n --> '0x'
        block.timestamp, // 1445130204n --> '0x5622efdc'
        block.extraData, // '0xd583010202844765746885676f312e35856c696e7578'
        block.mixHash, // '0x3fbea7af642a4e20cd93a945a1f5e23bd72fc5261153e09102cf718980aeff38'
        block.nonce // 7706288617141211887n --> '0x6af23caae95692ef'
    ];
    console.log(blockHeader);

    // 3. RLP encode the block header, turning it into uint8 array with 534 entries.
    const encodedHeader = RLP.encode(blockHeader); 
    console.log(encodedHeader);

    // 4. Convert encoded header to a Buffer and then to hex
    const encodedHeaderHex = Buffer.from(encodedHeader).toString('hex'); 
    console.log(encodedHeaderHex);

    // 5. Hash the encodedHeaderHex to verify block hash
    const blockHash = ethers.keccak256('0x' + encodedHeaderHex);
    console.log(blockHash); // Successfully matches the actal block hash!
  }
verifyHashOldBlocks();




// Works from 12,965,000 until the merge (15,537,393). Does not work on merge block 15,537,394 and beyond.
async function verifyHashModeratelyOldBlocks() {
    // 1. Get the full block
    const block = await web3.eth.getBlock(14000000); 
    console.log(block.hash); // '

    // 2. Make array with only block header items. Any numbers are converted to hex.
    const blockHeader = [
        block.parentHash, // '0x0c9ef41f038aa58a4aa2810fda03d9d82aac9082c80283230fd74cb1cceb4b00'
        block.sha3Uncles, // '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'
        block.miner, // '0xea674fdde714fd979de3edf0f56aa9716b898ec8'
        block.stateRoot, // '0xfb3df73e7a41d500a374ee236dd613bb24f1a5fa5b80615a7953ea75c26c6a6d'
        block.transactionsRoot, // '0x16b6ff83df3ef14f614c70ac29e8a05d102c6bed0e5882c284abf0120b89529c'
        block.receiptsRoot, // '0xf7b9f8e92a4b420a27302ab91084e5499d3d711183a021f4956ff32de246e14e'
        block.logsBloom, // '0x083815041300081040f485088003de10046040491c3c0164189830ca3c2c0d1121040493008ba600924849820c8901130b0084038802610706000b6101b220d01210000400020b08480850880d0226a04024e107005114513410021080080451126063400a1084021c62bc08290008a0430081540c892545360c401410194021426103228249162800ac016601100d0520b96203432080086a00424d8214010b63180471318634d0ae00108041a1184488a10841a2610e0401e9001a504a4010098182020808155027009c02008e56070c28728114480490400801cac010290ae291a00ca0408b60204c128012601c100a24cc01e100897242832a825000c1e1'
        block.difficulty, // 12316581093827601n --> '0x2bc1dd80f1e411'
        block.number, // 14000000n --> '0xd59f80'
        block.gasLimit, // 30058561n --> '0x1caa841'
        block.gasUsed, // 8119826n --> '0x7be612'
        block.timestamp, // 1642114795n --> '0x61e0aeeb'
        block.extraData, // '0x6175737472616c69612d736f75746865617374312d31'
        block.mixHash, // '0xa832679fcf3e71f0b29bd5913955151be5bf1fc59e135e3c7eb6e30f8442a5df'
        block.nonce, // 3596099692050383908n --> '0x31e7e99df18e0424'
        block.baseFeePerGas, // 139541559304n --> '0x207d533808' 
    ];
    console.log(blockHeader);

    // 3. RLP encode the block header, turning it into uint8 array with 545 indices.
    const encodedHeader = RLP.encode(blockHeader); 
    console.log(encodedHeader);

    // 4. Convert encoded header to a Buffer and then to hex so we can hash.
    const encodedHeaderHex = Buffer.from(encodedHeader).toString('hex'); 
    console.log(encodedHeaderHex);

    // 5. Hash the encodedHeaderHex to verify block hash
    const recreatedBlockHash = ethers.keccak256('0x' + encodedHeaderHex);
    console.log(recreatedBlockHash); // Successfully matches the actal block hash!
  }
verifyHashModeratelyOldBlocks();

1 Answer 1

2

As of May 7th 2024 (through Cancun), this is the solution for recreating the correct block hash for all 5 different "Eras" of block headers. The block hash from each era is calculated differently. If the header changes again in the future, the block.hash will likely change and be calculated accordingly.

Current Dependencies: assert, rlp v3.0.0, ethers v6.12.1, Web3 v4.8.0, dotenv. Using alchemy API URL with mainnet API key that currently looks like this: "https://eth-mainnet.g.alchemy.com/v2/xxxxxYOUR_API_KEY_HERExxxxx"

Here are all 5 calculations for mainnet Ethereum:

const assert = require('assert');
const RLP = require('rlp');
const { ethers } = require("ethers");
const { Web3 } = require('web3');
require("dotenv").config(); 

const web3 = new Web3(process.env.RPC_URL_MAINNET);

// [19426587, Current] inclusive. Cancun-Deneb --> Now (Working up to May 7th, 2024) 
async function verifyCancunToCurrentHash() {

    // 1. Get the full block
    const block = await web3.eth.getBlock(19800000);
    console.log(block.hash); // '0x95d7f597b43f97bb4dcb0f1d9a74f13d6d6236592cd01d122945d04b5a2aabad'

    // 2. Make array with only block header items.
    const blockHeader = [
        block.parentHash, // '0x4c0f381da82f7c5232f921308b040f2f51475040db7b1ada1970960872182b44'
        block.sha3Uncles, // '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'
        block.miner, // '0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5'
        block.stateRoot, // '0xe08eb6a130d0ab2b301b63ebb512ba47cd55662d3fe403341ea42dc79665613c'
        block.transactionsRoot, // '0xb710f4a7c9917d3e81f0cfdbbe9ea5854db0e745d37194d2342d3ea0585d285f'
        block.receiptsRoot, // '0x27bfbe21bcb21d27ff8f5d442b4e5110080b3df4e1d1f0b6314d77605e9f7e6e'
        block.logsBloom, // '0x2723022003b74129109181308d241b0014a9489122d42100820822154c6c5b18811111863001a228625c1ae2c81c418b4aa5880fae43b80054b8182a00fa20025002300d4c081aad49cad24b8830c0b794c5805003427cd4b114154582695007c0c829012631b70205c8441510a3ed5d020e10390c400e01a8c50495046862206a328e505070a1044001c642c9382106882104810dc8e208662300dc6e74448e4fb20042748a28244d0cc6e61909842466618640d89d58000a726d0821c80140113031176cc24cc810018e35590bfc4e28c0a03e462220504a6654222240a0c2913bb40510030dd0941c16c3ba0688458528d2c89db43440a20cf410160854c7'
        block.difficulty, // 0n ('0x')
        block.number, // 19800000n ('0x12e1fc0')
        block.gasLimit, // 30000000n ('0x1c9c380')
        block.gasUsed, // 8020412n ('0x7a61bc')
        block.timestamp, // 1714865051n ('0x6636c39b')
        block.extraData, // '0x6265617665726275696c642e6f7267'
        block.mixHash, // '0xb50774a2180b910c41018b5651e87200c3d10c7b7cd0443b20e346b3f289b66a'
        '0x0000000000000000', //block.nonce MUST BE 16 ZEROs IN THIS ERA!
        block.baseFeePerGas, // 5007423601n ('0x12a773871')
        block.withdrawalsRoot, // '0x1a470d20b701c3a7f5272198de31ac4a769ffc4dd0637e0c9718c0adf5c346f5' 
        block.blobGasUsed, // 786432n ('0xc0000')
        block.excessBlobGas, // 0n ('0x')
        block.parentBeaconBlockRoot, // '0x81fcf3a2ae3a5543a467906ebc642cc9fc7d18fd094a253ea5349323b87494a2'
    ];
    console.log(blockHeader);

    // 3. RLP encode the block header, turning it into uint8 array.
    const encodedHeader = RLP.encode(blockHeader); 
    console.log(encodedHeader);

    // 4. Convert encoded header to a Buffer and then to hex so we can hash it.
    const encodedHeaderHex = Buffer.from(encodedHeader).toString('hex'); 
    console.log(encodedHeaderHex);

    // 5. Hash the encodedHex to verify block hash
    const recreatedBlockHash = ethers.keccak256('0x' + encodedHeaderHex);
    console.log(recreatedBlockHash); 
    assert.deepStrictEqual(recreatedBlockHash, block.hash); // Successfully matches the actal block hash!
}
verifyCancunToCurrentHash();



// [17034870, 19426586] inclusive. Shanghai-Capella --> Cancun-Deneb
async function verifyShanghaiToCancunHash() {

    // 1. Get the full block
    const block = await web3.eth.getBlock(19010121);
    console.log(block.hash); // '0x8576cd4900e56c1214e2f32fbb194c0ebddde8cffd243194187977583d712aa5'

    // 2. Make array with only block header items.
    const blockHeader = [
        block.parentHash, // '0x0607c9891abee48a5d70948d61d41ef7cabe554a8dd1aec49c6322ba7e2fab25'
        block.sha3Uncles, // '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'
        block.miner, // '0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5'
        block.stateRoot, // '0x0f90554fb8e825ce12c6ad7f0d5564e0197d6b41b4d6cf2a65175373795ef1fa'
        block.transactionsRoot, // '0xce8641939cdf1811521940f78609856d43a8eaf4e7b5c0a48e24c242cd57ec4b'
        block.receiptsRoot, // '0x52c2389675794511f8787f7a999019d9bfe9bfa9c6ec3b8544592241387d52aa'
        block.logsBloom, // '0x10a1d45047215b70ca08a790aa40920729bb9280ea453316106f832056a109a0193404ed0c01026300305933a870495813258a488813a4b8669d4644c0682180910ab60909c48d0a6b83c04e022c34f06199085db4fe19811143e213c832cd7209850550260ec14204c22a0c64646f020a30a8c01e008400c61621d4420850566032045603891962784d03d201620734430308930fb9130804c201437b700c622f43b1da4c80600365c04c86f8800cc40602a42103bc00222131a90601ce040055044e035af412610422ba2310c80a4c2dc023729669081405df1546c10120130098e001394c0102286c50252810ad44dea07930211814440a009c3c80630c21'
        block.difficulty, // 0n ('0x')
        block.number, // 19010121n ('0x1221249')
        block.gasLimit, // 30000000n ('0x1c9c380')
        block.gasUsed, // 12044352n ('0xb7c840')
        block.timestamp, // 1705295603n ('0x65a4bef3')
        block.extraData, // '0x6265617665726275696c642e6f7267'
        block.mixHash, // '0x84376d868db109d93e3062b04daa8a4f50e8ac872a0807eb740d306a395dc7d6'
        '0x0000000000000000', //block.nonce MUST BE 16 ZEROs IN THIS ERA!
        block.baseFeePerGas, // 16698450961n ('0x3e34e2411')
        block.withdrawalsRoot, // '0x3e72aaa4c11c1c04da24b4e2e40709b8a0795e877997921ab5806acd890c1054'
    ];
    console.log(blockHeader);

    // 3. RLP encode the block header, turning it into uint8 array with 534 entries.
    const encodedHeader = RLP.encode(blockHeader); 
    console.log(encodedHeader);

    // 4. Convert encoded header to a Buffer and then to hex so we can hash it.
    const encodedHeaderHex = Buffer.from(encodedHeader).toString('hex'); 
    console.log(encodedHeaderHex);

    // 5. Hash the encodedHex to verify block hash
    const recreatedBlockHash = ethers.keccak256('0x' + encodedHeaderHex);
    console.log(recreatedBlockHash);
    assert.deepStrictEqual(recreatedBlockHash, block.hash); // Successfully matches the actal block hash!
  }
//verifyShanghaiToCancunHash();


// [15537394, 17034869] inclusive. Paris --> Shanghai
async function verifyHashParisToShanghai() {

    // 1. Get the full block
    const block = await web3.eth.getBlock(16000000); 
    console.log(block.hash); // '0x3dc4ef568ae2635db1419c5fec55c4a9322c05302ae527cd40bff380c1d465dd'

    // 2. Make array with only block header items.
    const blockHeader = [
        block.parentHash, // '0x6f377dc6bd1f3e38b9ceb8c946a88c13211fa3f084622df3ee5cfcd98cc6bb16'
        block.sha3Uncles, // '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'
        block.miner, // '0xebec795c9c8bbd61ffc14a6662944748f299cacf'
        block.stateRoot, // '0xe5608defce0c3e193b4c2e3452ece5158e6ae35db211925cdfb4cd307587bbf0'
        block.transactionsRoot, // '0xe0265e44b4639453428546d1c0046c9fbba7d679b7be3e67692904c776389890'
        block.receiptsRoot, // '0x63c77297d9aace97c33e40c07c4f7d52f62e898f9be74f43ae8f8b751012e719'
        block.logsBloom, // '0xdaa80d34c104520cb02c181aa334f26395a0512141a85800c11d8c0c9d7729450f0c5312194a10655d72ba6542aab72f070146682f1ae981e663c1ff58eaedc266044692052ab00e7eaed34b60b26cf85003080153c602690e38ac619e4461431ac01d6c5a070e28c9469e006a90999175badc76a93b2e3a56b300d8ca9e18108e22c319caa21081184b494880923617c1800cf95b24904be3a48079c4526582fee283c1100ae8112239a1c908ae804a4dc178cee0248d8215a33d0c91c895555194c1abe11124dd81c09a02018fa22e4de90fcb90db62160058bb02c0b46054603eb1889038316a1211de9010db1bd1971c0085cc2c1642100f096618daf833'
        block.difficulty, // 0n ('0x')
        block.number, // 16000000n ('0xf42400')
        block.gasLimit, // 30000000n ('0x1c9c380')
        block.gasUsed, // 18992639n ('0x121cdff')
        block.timestamp, // 1668811907n ('0x63780c83')
        block.extraData, // '0x4d616465206f6e20746865206d6f6f6e20627920426c6f636b6e6174697665'
        block.mixHash, // '0xe197021bc2912013a6c5e3a42fa1260f00d80ad389a9f7137a5dafdaef38977a'
        '0x0000000000000000', // block.nonce. MUST HAVE 16 ZEROs IN THIS ERA
        block.baseFeePerGas, // 11130414489n ('0x2976ca599')
    ];
    console.log(blockHeader);

    // 3. RLP encode the block header, turning it into uint8 array.
    const encodedHeader = RLP.encode(blockHeader); 
    console.log(encodedHeader);

    // 4. Convert encoded header to a Buffer and then to hex so we can hash it.
    const encodedHeaderHex = Buffer.from(encodedHeader).toString('hex'); 
    console.log(encodedHeaderHex);

    // 5. Hash the encodedHeaderHex to verify block hash
    const recreatedBlockHash = ethers.keccak256('0x' + encodedHeaderHex);
    console.log(recreatedBlockHash);
    assert.deepStrictEqual(recreatedBlockHash, block.hash); // Successfully matches the actal block hash!
  }
//verifyHashParisToShanghai();



// [12965000, 15537393] inclusive. London --> Paris (The Merge).
async function verifyHashLondonToParisBlocks() {

    // 1. Get the full block
    const block = await web3.eth.getBlock(14000000); 
    console.log(block.hash); // '

    // 2. Make array with only block header items.
    const blockHeader = [
        block.parentHash, // '0x0c9ef41f038aa58a4aa2810fda03d9d82aac9082c80283230fd74cb1cceb4b00'
        block.sha3Uncles, // '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'
        block.miner, // '0xea674fdde714fd979de3edf0f56aa9716b898ec8'
        block.stateRoot, // '0xfb3df73e7a41d500a374ee236dd613bb24f1a5fa5b80615a7953ea75c26c6a6d'
        block.transactionsRoot, // '0x16b6ff83df3ef14f614c70ac29e8a05d102c6bed0e5882c284abf0120b89529c'
        block.receiptsRoot, // '0xf7b9f8e92a4b420a27302ab91084e5499d3d711183a021f4956ff32de246e14e'
        block.logsBloom, // '0x083815041300081040f485088003de10046040491c3c0164189830ca3c2c0d1121040493008ba600924849820c8901130b0084038802610706000b6101b220d01210000400020b08480850880d0226a04024e107005114513410021080080451126063400a1084021c62bc08290008a0430081540c892545360c401410194021426103228249162800ac016601100d0520b96203432080086a00424d8214010b63180471318634d0ae00108041a1184488a10841a2610e0401e9001a504a4010098182020808155027009c02008e56070c28728114480490400801cac010290ae291a00ca0408b60204c128012601c100a24cc01e100897242832a825000c1e1'
        block.difficulty, // 12316581093827601n ('0x2bc1dd80f1e411')
        block.number, // 14000000n ('0xd59f80')
        block.gasLimit, // 30058561n ('0x1caa841')
        block.gasUsed, // 8119826n ('0x7be612')
        block.timestamp, // 1642114795n ('0x61e0aeeb')
        block.extraData, // '0x6175737472616c69612d736f75746865617374312d31'
        block.mixHash, // '0xa832679fcf3e71f0b29bd5913955151be5bf1fc59e135e3c7eb6e30f8442a5df'
        block.nonce, // 3596099692050383908n ('0x31e7e99df18e0424')
        block.baseFeePerGas, // 139541559304n ('0x207d533808')
    ];
    console.log(blockHeader);

    // 3. RLP encode the block header, turning it into uint8 array.
    const encodedHeader = RLP.encode(blockHeader); 
    console.log(encodedHeader);

    // 4. Convert encoded header to a Buffer and then to hex so we can hash it.
    const encodedHeaderHex = Buffer.from(encodedHeader).toString('hex'); 
    console.log(encodedHeaderHex);

    // 5. Hash the encodedHeaderHex to verify block hash
    const recreatedBlockHash = ethers.keccak256('0x' + encodedHeaderHex);
    console.log(recreatedBlockHash);
    assert.deepStrictEqual(recreatedBlockHash, block.hash); // Successfully matches the actal block hash!
  }
//verifyHashLondonToParisBlocks();


// [1, 12964999] inclusive. Genesis --> London. 
async function verifyHashGenesisToLondonBlocks() {

    // 1. Get the full block
    const block = await web3.eth.getBlock(400000);
    console.log(block.hash); // '0x5d15649e25d8f3e2c0374946078539d200710afc977cdfc6a977bd23f20fa8e8'

    // 2. Make array with only block header items.
    const blockHeader = [
        block.parentHash, // '0x1e77d8f1267348b516ebc4f4da1e2aa59f85f0cbd853949500ffac8bfc38ba14'
        block.sha3Uncles, // '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'
        block.miner, // '0x2a65aca4d5fc5b5c859090a6c34d164135398226'
        block.stateRoot, // '0x0b5e4386680f43c224c5c037efc0b645c8e1c3f6b30da0eec07272b4e6f8cd89'
        block.transactionsRoot, // '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'
        block.receiptsRoot, // '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'
        block.logsBloom, // '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
        block.difficulty, // 6022643743806n ('0x57a418a7c3e')
        block.number, // 400000n ('0x61a80')
        block.gasLimit, // 3141592n ('0x2fefd8')
        block.gasUsed, // 0n ('0x')
        block.timestamp, // 1445130204n ('0x5622efdc')
        block.extraData, // '0xd583010202844765746885676f312e35856c696e7578'
        block.mixHash, // '0x3fbea7af642a4e20cd93a945a1f5e23bd72fc5261153e09102cf718980aeff38'
        block.nonce // 7706288617141211887n ('0x6af23caae95692ef')
    ];
    console.log(blockHeader);

    // 3. RLP encode the block header, turning it into uint8 array.
    const encodedHeader = RLP.encode(blockHeader); 
    console.log(encodedHeader);

    // 4. Convert encoded header to a Buffer and then to hex so we can hash it.
    const encodedHeaderHex = Buffer.from(encodedHeader).toString('hex'); 
    console.log(encodedHeaderHex);

    // 5. Hash the encodedHeaderHex to verify block hash
    const recreatedBlockHash = ethers.keccak256('0x' + encodedHeaderHex);
    console.log(recreatedBlockHash);
    assert.deepStrictEqual(recreatedBlockHash, block.hash); // Successfully matches the actal block hash!
  }
//verifyHashGenesisToLondonBlocks();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.