Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions test/helpers/trie.js
Original file line number Diff line number Diff line change
@@ -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 };
53 changes: 17 additions & 36 deletions test/utils/cryptography/TrieProof.test.js
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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;
}
});

Expand Down