diff --git a/test/helpers/trie.js b/test/helpers/trie.js new file mode 100644 index 00000000000..c5f7fb176f0 --- /dev/null +++ b/test/helpers/trie.js @@ -0,0 +1,81 @@ +const { ethers } = require('ethers'); +const { MerklePatriciaTrie, createMerkleProof } = require('@ethereumjs/mpt'); + +class BlockTries { + constructor(block) { + this.block = block; + + this.transactionTrie = new MerklePatriciaTrie(); + this.receiptTrie = new MerklePatriciaTrie(); + + this._ready = Promise.all( + block.transactions.map(hash => + block.getTransaction(hash).then(tx => + Promise.all([ + // Transaction + this.transactionTrie.put(BlockTries.indexToKeyBytes(tx.index), BlockTries.serializeTransaction(tx)), + // Receipt + tx + .wait() + .then(receipt => + this.receiptTrie.put(BlockTries.indexToKeyBytes(tx.index), BlockTries.serializeReceipt(receipt)), + ), + ]), + ), + ), + ).then(() => this); + } + + ready() { + return this._ready; + } + + getTransactionProof(index) { + return this.ready().then(() => createMerkleProof(this.transactionTrie, BlockTries.indexToKeyBytes(index))); + } + + getReceiptProof(index) { + return this.ready().then(() => createMerkleProof(this.receiptTrie, BlockTries.indexToKeyBytes(index))); + } + + get transactionTrieRoot() { + return ethers.hexlify(this.transactionTrie.root()); + } + + get receiptTrieRoot() { + return ethers.hexlify(this.receiptTrie.root()); + } + + static from(block) { + const instance = new BlockTries(block); + return instance.ready().then(() => instance); + } + + // Serialize a transaction into its RLP encoded form + static serializeTransaction(tx) { + return ethers.Transaction.from(tx).serialized; + } + + // Serialize a receipt into its RLP encoded form + static serializeReceipt(receipt) { + return ethers.concat([ + receipt.type === 0 ? '0x' : ethers.toBeHex(receipt.type), + ethers.encodeRlp([ + receipt.status === 0 ? '0x' : '0x01', + ethers.toBeHex(receipt.cumulativeGasUsed), + receipt.logsBloom, + receipt.logs.map(log => [log.address, log.topics, log.data]), + ]), + ]); + } + + static indexToKey(index) { + return ethers.encodeRlp(ethers.stripZerosLeft(ethers.toBeHex(index))); + } + + static indexToKeyBytes(index) { + return ethers.getBytes(BlockTries.indexToKey(index)); + } +} + +module.exports = { BlockTries }; diff --git a/test/utils/cryptography/TrieProof.test.js b/test/utils/cryptography/TrieProof.test.js index 78e961def20..f980a52a8d7 100644 --- a/test/utils/cryptography/TrieProof.test.js +++ b/test/utils/cryptography/TrieProof.test.js @@ -1,11 +1,11 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { spawn } = require('child_process'); -const { MerklePatriciaTrie, createMerkleProof } = require('@ethereumjs/mpt'); const { Enum } = require('../../helpers/enums'); const { zip } = require('../../helpers/iterate'); const { generators } = require('../../helpers/random'); +const { BlockTries } = require('../../helpers/trie'); const { batchInBlock } = require('../../helpers/txpool'); const ProofError = Enum( @@ -82,44 +82,25 @@ describe('TrieProof', function () { false, ]); - // Rebuild tries - const transactionTrie = new MerklePatriciaTrie(); - const receiptTrie = new MerklePatriciaTrie(); - - for (const tx of txs) { - const key = ethers.encodeRlp(ethers.stripZerosLeft(ethers.toBeHex(tx.index))); - - // Transaction - const encodedTransaction = await tx.getTransaction().then(tx => ethers.Transaction.from(tx).serialized); - await transactionTrie.put(ethers.getBytes(key), encodedTransaction); - - // Receipt - const encodedReceipt = ethers.concat([ - tx.type === 0 ? '0x' : ethers.toBeHex(tx.type), - ethers.encodeRlp([ - tx.status === 0 ? '0x' : '0x01', - ethers.toBeHex(tx.cumulativeGasUsed), - tx.logsBloom, - tx.logs.map(log => [log.address, log.topics, log.data]), - ]), - ]); - await receiptTrie.put(ethers.getBytes(key), encodedReceipt); - - Object.assign(tx, { key, encodedTransaction, encodedReceipt }); - } + const blockTries = await this.provider.getBlock(txs.at(0).blockNumber).then(BlockTries.from); // Sanity check trie roots - expect(ethers.hexlify(transactionTrie.root())).to.equal(transactionsRoot); - expect(ethers.hexlify(receiptTrie.root())).to.equal(receiptsRoot); + expect(blockTries.transactionTrieRoot).to.equal(transactionsRoot); + expect(blockTries.receiptTrieRoot).to.equal(receiptsRoot); - // Verify transaction inclusion in the block's transaction trie - for (const { key, encodedTransaction, encodedReceipt } of txs) { - const transactionProof = await createMerkleProof(transactionTrie, ethers.getBytes(key)); - await expect(this.mock.$verify(encodedTransaction, transactionsRoot, key, transactionProof)).to.eventually.be - .true; - - const receiptProof = await createMerkleProof(receiptTrie, ethers.getBytes(key)); - await expect(this.mock.$verify(encodedReceipt, receiptsRoot, key, receiptProof)).to.eventually.be.true; + for (const tx of txs) { + // verify transaction inclusion in the block's transaction trie + const transaction = await tx.getTransaction().then(BlockTries.serializeTransaction); + const transactionProof = await blockTries.getTransactionProof(tx.index); + await expect( + this.mock.$verify(transaction, transactionsRoot, BlockTries.indexToKey(tx.index), transactionProof), + ).to.eventually.be.true; + + // verify receipt inclusion in the block's receipt trie + const receipt = BlockTries.serializeReceipt(tx); + const receiptProof = await blockTries.getReceiptProof(tx.index); + await expect(this.mock.$verify(receipt, receiptsRoot, BlockTries.indexToKey(tx.index), receiptProof)).to + .eventually.be.true; } });