As an exercise I want to retrieve all transactions from a block and rebuild the merkle patricia trie, then compare the root hash with the transactionRoot stored in the block. My current logic is receive block with complete transactions and then store each one in the trie using the necessary rlp encoding and serialization but I do not recreate the hash stored in the block.

for reference the block i am using can be found here the block hash is 0x441bcda29969fe4c94790b8538b70e30b66c62b69fe6516bea99634d290d16c7

the four block transactions in raw format (from etherscan) are: tx0, tx1, tx2, tx3

const { bufferToHex } = require("@ethereumjs/util");
const {Trie} = require("@ethereumjs/trie");
const {
    AccessListEIP2930Transaction: EIP2930Transaction,
    FeeMarketEIP1559Transaction: EIP1559Transaction,
    Transaction: LegacyTransaction,
} = require("@ethereumjs/tx");

const RLP = require("rlp");

const ethers = require("ethers");

const trie = new Trie();

    block that contains only 4 transactions 
const blockHash = '0x441bcda29969fe4c94790b8538b70e30b66c62b69fe6516bea99634d290d16c7';

    all 4 raw transactions copied from etherscan 

const rawTxs = {



    const provider = new ethers.providers.JsonRpcProvider();

    const block = await provider.send("eth_getBlockByHash",[ blockHash, true ]);
    const results = await Promise.all(block.transactions.map((tx, i) => 
            (async (tx, i) => {

                    in block returned a transaction has 2 properties 'input' and 'gas'
                    that are expected as 'data' and 'gasLimit' 
                    modifications for ethereumjs 

                tx.data = tx.input;
                delete tx.input;
                tx.gasLimit = tx.gas;
                delete tx.gas;

                let TransactionClass;

                switch (tx.type){
                    case '0x2' :
                    case '0x1' :
                    case '0x0' :

                const serializedTx = TransactionClass.fromTxData(tx).serialize();

                    check that serialized transaction matches etherscan raw transaction

                console.log(rawTxs[`tx${i}`] === bufferToHex(serializedTx));

                return await trie.put(

            })(tx, i) 

        console.log(`block.transactionsRoot: ${block.transactionsRoot}`)
        console.log(`trie.root: ${bufferToHex(trie.root)}`)        

  • 1
    In order to rebuild the merkle patricia trie, you need to have the merkle patricia trie from the previous block. You can't just recreate the world state with the current block. You need to take the entire history into account.
    – Donut
    Commented Jul 11, 2022 at 3:56
  • @Donut thank you for your response. I'm getting closer I think but I'm still not there. When you say you need the MPT of the previous block how do you mean exactly? I amended the above code to compare the transaction root with ancestor blocks but clearly that is naive. Do I have to include the transactions of another block when building the trie? Commented Jul 11, 2022 at 6:28
  • In order to get the current world state, you are required to start from block 1 and the transactions all the way to the current block. Imagine that block 1 has a transaction where 3 Eth are transferred from address 0x01 to 0x02. And that's it, these addresses are never touched again. The accounts are still part of the world state, but you will only get them in there if you replay the transaction from block 1. Does that make sense?
    – Donut
    Commented Jul 11, 2022 at 7:27

1 Answer 1


My error was using @ethereumjs/trie's SecureTrie class instead of Trie the above code works now

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.