added pmt builder considering the witnesses#44
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
3c823ca to
252f877
Compare
There was a problem hiding this comment.
Pull request overview
Adds support tooling to build Partial Merkle Trees using witness transaction IDs (wtxids), and extends the existing “registerBtcTransaction” helper to also return a PMT computed over wtxids.
Changes:
- Added
getWtxidhelper for computing wtxids from raw transaction hex. - Added a new CLI tool to build a PMT using wtxids (
tool/pmt-witness-builder.js). - Extended
getInformationReadyForRegisterBtcTransactionto return an additionalpmtConsideringWitnessfield and added a unit test forgetWtxid.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| tool/pmt-witness-builder.js | New CLI script that builds a PMT using wtxids for a block containing a target tx. |
| tool/pmt-builder-utils.js | New helper for deriving wtxid from raw tx hex via bitcoinjs-lib. |
| tool/getInformationReadyForRegisterBtcTransaction.js | Now computes both txid-based PMT and wtxid-based PMT and returns both. |
| test/test.js | Adds unit coverage for getWtxid and updates require style. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,9 @@ | |||
| const bitcoin = require('bitcoinjs-lib'); | |||
There was a problem hiding this comment.
This tool code depends on bitcoinjs-lib at runtime (via require('bitcoinjs-lib')), but bitcoinjs-lib is currently listed under devDependencies. If these tool/* scripts are intended to be usable from an installed package (without devDependencies), move bitcoinjs-lib to dependencies or document that tools require a dev install.
There was a problem hiding this comment.
So far, all the code in /tool is just to use locally, not to be installed and used from another project. So, bitcoinjs-lib as a dev dependency is acceptable for now.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tool/pmt-builder-utils.js
Outdated
| const tx = bitcoin.Transaction.fromHex(rawTx); | ||
| const wtxid = tx.getHash(true).reverse().toString('hex'); | ||
| return wtxid; | ||
| } |
There was a problem hiding this comment.
getWtxid is missing a trailing semicolon after the function expression. The rest of this module uses semicolons consistently, so this stands out and can cause style/lint inconsistencies.
| } | |
| }; |
|
|
||
| const pmt = resultPmt.hex; | ||
| const blockHeight = transaction.status.block_height; | ||
| const rawTargetBtcTransaction = await getTransactionWithRetry(transactionsClient, transactionHash); |
There was a problem hiding this comment.
Same issue for the target transaction: rawTargetBtcTransaction can be null from getTransactionWithRetry, which will break getWtxid and also produce tx: 0xnull in the returned object. Handle the null case explicitly (e.g., throw with txid context) before computing targetWtxid / building the witness PMT.
| const rawTargetBtcTransaction = await getTransactionWithRetry(transactionsClient, transactionHash); | |
| const rawTargetBtcTransaction = await getTransactionWithRetry(transactionsClient, transactionHash); | |
| if (rawTargetBtcTransaction == null) { | |
| throw new Error(`Failed to fetch raw Bitcoin transaction for txid ${transactionHash}`); | |
| } |
| (async () => { | ||
| try { | ||
|
|
||
| const network = process.argv[2]; | ||
| const transactionHash = process.argv[3]; | ||
|
|
There was a problem hiding this comment.
This module executes the CLI IIFE unconditionally. Importing/requiring it from another script will trigger network calls and console output as a side effect. Guard the CLI entry with if (require.main === module) (or move the CLI to a separate entry file) so the exported function can be reused safely.
There was a problem hiding this comment.
so far, each file is an independent tool
tool/pmt-witness-builder.js
Outdated
| const txid = blockTxIds[i]; | ||
| const rawTx = await getTransactionWithRetry(transactionsClient, txid); | ||
| const wtxid = getWtxid(rawTx); | ||
| blockWtxids.push(wtxid); |
There was a problem hiding this comment.
getTransactionWithRetry can return null; calling getWtxid(rawTx) will throw and make it hard to diagnose which tx in the block failed to fetch. Add an explicit null check here (include the txid in the error) before computing/pushing the wtxid.
tool/pmt-witness-builder.js
Outdated
| const rawTx = await getTransactionWithRetry(transactionsClient, txHash); | ||
| const targetTxWTxId = getWtxid(rawTx); | ||
| const resultPmt = pmtBuilder.buildPMT(blockWtxids, targetTxWTxId); |
There was a problem hiding this comment.
Same null-handling issue for the target tx: rawTx can be null from getTransactionWithRetry, which will break getWtxid and PMT building. Handle the null case explicitly before computing targetTxWTxId / calling buildPMT.
| /** | ||
| * Retrieves all transaction objects in a block by their transaction IDs. | ||
| * @param {string[]} txIds - An array of txIds. | ||
| * @returns {Promise<string[]>} An array of transactions. |
There was a problem hiding this comment.
The getAllTxs JSDoc is now inaccurate: it says it returns Promise<string[]>, but the function pushes bitcoinJs.Transaction.fromHex(tx) and returns an array of Transaction objects. Update the return type/doc to match the actual return value.
| * @returns {Promise<string[]>} An array of transactions. | |
| * @returns {Promise<import('bitcoinjs-lib').Transaction[]>} A promise that resolves to an array of bitcoinjs-lib Transaction objects. |
| // mempool.js getTx method returns an object that directly contains 'wtxid' | ||
| const tx = await getTransactionWithRetry(txId); | ||
| const tx = await getTransactionWithRetry(transactionsClient, txId); |
There was a problem hiding this comment.
This inline comment is misleading: it references mempool.js getTx returning an object with wtxid, but the code here calls getTransactionWithRetry(...) which uses getTxHex and returns raw hex. Please update/remove the comment to reflect what’s actually happening.
| (async () => { | ||
| try { | ||
| const network = process.argv[2]; | ||
| const txHash = process.argv[3]; | ||
|
|
||
| const pmtInformation = await getPmtInformationWithWitness(network, txHash); | ||
|
|
||
| console.log('PMT information considering wtxid: ', pmtInformation); | ||
|
|
||
| } catch (e) { | ||
| console.log(e); | ||
| } | ||
| })(); |
There was a problem hiding this comment.
This file runs its CLI IIFE unconditionally. If it’s ever imported/reused programmatically, it will immediately perform network calls and write to stdout. Consider guarding the CLI entry with if (require.main === module) (or move the CLI into a dedicated entry file) to keep the module side-effect free on import.
| (async () => { | |
| try { | |
| const network = process.argv[2]; | |
| const txHash = process.argv[3]; | |
| const pmtInformation = await getPmtInformationWithWitness(network, txHash); | |
| console.log('PMT information considering wtxid: ', pmtInformation); | |
| } catch (e) { | |
| console.log(e); | |
| } | |
| })(); | |
| if (require.main === module) { | |
| (async () => { | |
| try { | |
| const network = process.argv[2]; | |
| const txHash = process.argv[3]; | |
| const pmtInformation = await getPmtInformationWithWitness(network, txHash); | |
| console.log('PMT information considering wtxid: ', pmtInformation); | |
| } catch (e) { | |
| console.log(e); | |
| } | |
| })(); | |
| } |
tool/pmt-witness-builder.js
Outdated
| const targetTxWTxId = getWtxid(rawTx); | ||
| const resultPmt = pmtBuilder.buildPMT(blockWtxids, targetTxWTxId); |
There was a problem hiding this comment.
Variable naming is inconsistent here (targetTxWTxId). Since this value is a wtxid, consider renaming to something like targetWtxid for clarity and consistency with blockWtxids.
| const targetTxWTxId = getWtxid(rawTx); | |
| const resultPmt = pmtBuilder.buildPMT(blockWtxids, targetTxWTxId); | |
| const targetWtxid = getWtxid(rawTx); | |
| const resultPmt = pmtBuilder.buildPMT(blockWtxids, targetWtxid); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
tool/getInformationReadyForRegisterCoinbaseBtcTransaction.js:106
- Witness merkle roots per BIP141 treat the coinbase leaf’s wtxid as 32 bytes of
00..00(not the actual coinbase wtxid).hashesWithWitnessis currently built fromtx.getHash(true)for every tx including coinbase, so the computedwitnessMerkleRootwill not match the block’s witness commitment. Special-case index 0 toBuffer.alloc(32, 0)before building the merkle tree.
// Calculate witnessRoot
const hashesWithWitness = txs.map( x => Buffer.from(x.getHash(true)));
const witnessMerkleTree = merkleLib(hashesWithWitness, bitcoinJs.crypto.hash256);
// Get witness merkleRoot from witnessMerkleTree. This is equal to the last element in witnessMerkleTree array
const witnessMerkleRoot = witnessMerkleTree[witnessMerkleTree.length-1].reverse();
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Retrieves all transaction objects in a block by their transaction IDs. | ||
| * @param {Object} transactionsClient - Client instance used to fetch transaction data (must provide `getTxHex`). | ||
| * @param {string[]} txIds - An array of txIds. | ||
| * @returns {Promise<string[]>} An array of transactions. |
There was a problem hiding this comment.
The getAllTxs JSDoc says it returns Promise<string[]>, but the implementation pushes bitcoinJs.Transaction.fromHex(tx) and returns an array of Transaction objects. Please update the return type to match the actual return value (e.g., Promise<bitcoinjs-lib.Transaction[]>).
| * @returns {Promise<string[]>} An array of transactions. | |
| * @returns {Promise<bitcoinJs.Transaction[]>} An array of transactions. |
tool/pmt-witness-builder.js
Outdated
| const blockWtxids = []; | ||
| for (let i = 0; i < blockTxIds.length; i++) { | ||
| const txid = blockTxIds[i]; | ||
| const rawTx = await getTransactionWithRetry(transactionsClient, txid); | ||
|
|
||
| if (!rawTx) { | ||
| throw new Error(`Failed to fetch transaction details for txId: ${txid}. It might not exist or is malformed.`); | ||
| } | ||
|
|
||
| const wtxid = getWtxid(rawTx); | ||
| blockWtxids.push(wtxid); | ||
|
|
||
| if (i < blockTxIds.length - 1) { | ||
| await sleep(); | ||
| } |
There was a problem hiding this comment.
For the witness merkle tree (BIP141), the coinbase leaf must be the all-zero wtxid (32 bytes of 00..00). This loop currently computes and includes the actual coinbase wtxid, so the resulting witness PMT won’t correspond to the block’s witness commitment. Please special-case the first tx in the block (coinbase) to use the zero wtxid, and ensure targetWtxid is also zero when the target tx is coinbase.
…o modified the tests with real txs
tool/pmt-builder-utils.js
Outdated
| return await transactionsClient.getTxHex({txid: txId}); | ||
| } catch (error) { | ||
| // mempool.js might wrap the error, so we check for common indicators of 429 | ||
| const isRateLimitError = error.response && error.response.status === 429; |
There was a problem hiding this comment.
Should we put this 429 in a constant? TOO_MANY_REQUEST_ERROR_CODE or something like that.
jeremy-then
left a comment
There was a problem hiding this comment.
LGTM. Just left some suggestions.
tool/pmt-builder-utils.js
Outdated
| try { | ||
| return await transactionsClient.getTxHex({txid: txId}); | ||
| } catch (error) { | ||
| // mempool.js might wrap the error, so we check for common indicators of 429 |
There was a problem hiding this comment.
| // mempool.js might wrap the error, so we check for common indicators of 429 | |
| // mempool.js might wrap the error, so we check for common indicators of too many requests error code |
test/test.js
Outdated
| it('should return the txid as the wtxid for a block without witness transactions', async () => { | ||
| const blockTxids = ['2bf2c26cf756564f1d7f17f89882da32f20d694d9f2c8f04962c1116ccdf0bcf']; | ||
| const targetTxId = blockTxids[0]; | ||
| const transactionsClient = createTransactionsClientMock(); | ||
|
|
||
| const result = await getWtxids(transactionsClient, blockTxids, targetTxId); | ||
|
|
||
| expect(result.targetWtxid).to.equal(transactions[targetTxId].wtxid); | ||
| expect(result.blockWtxids.length).to.equal(1); | ||
| expect(result.blockWtxids).to.be.an('array').that.includes(transactions[targetTxId].wtxid); | ||
| }); | ||
|
|
||
| it('should return the witness hash for a block with witness transactions', async () => { |
There was a problem hiding this comment.
test with an array bigger than one tx?
a block with both txs with and without witness?
There was a problem hiding this comment.
I think intead of having 2 tests with 1 tx, I'll write 3 tests with many txs: 1 all with witness, 1 all without witness, and another mixed
test/test.js
Outdated
|
|
||
| const result = await getWtxids(transactionsClient, blockTxids, targetTxId); | ||
|
|
||
| expect(result.targetWtxid).to.equal(transactions[targetTxId].wtxid); |
There was a problem hiding this comment.
if the tx does not have witness shouldnt the wtxid be equal to the txid?
| expect(result.targetWtxid).to.equal(transactions[targetTxId].wtxid); | |
| expect(result.targetWtxid).to.equal(transactions[targetTxId].txid); |
tool/pmt-witness-builder.js
Outdated
| const bitcoin = mempoolJS({ | ||
| hostname: 'mempool.space', | ||
| network // 'testnet' | 'mainnet' | ||
| }); | ||
|
|
||
| const transactionsClient = bitcoin.bitcoin.transactions; | ||
| const blocksClient = bitcoin.bitcoin.blocks; | ||
|
|
||
| const transaction = await transactionsClient.getTx({ txid: txHash }); | ||
|
|
||
| const blockHash = transaction.status.block_hash; | ||
| const blockTxIds = await blocksClient.getBlockTxids({ hash: blockHash }); | ||
|
|
||
| const { blockWtxids, targetWtxid } = await getWtxids(transactionsClient, blockTxIds, txHash); | ||
| const resultPmt = pmtBuilder.buildPMT(blockWtxids, targetWtxid); | ||
| return resultPmt; |
There was a problem hiding this comment.
except for the getWtxids part, you have almost the exact same code in pmt-builder
There was a problem hiding this comment.
Done. Also, I extracted shared behavior between the two getInformationReadyForRegister tools. However, I am not sure about having a method getBlockInfoByTransactionHash that returns an object with multiple elements. So consider this solution as an alternative. wdyt? aaf2d0f
| const isRateLimitError = error.response && error.response.status === TOO_MANY_REQUESTS_ERROR_CODE; | ||
|
|
||
| // Sometimes the error message might contain clues if status is not directly 429 | ||
| // Sometimes the error message might contain clues if status code is not directly 429 |
There was a problem hiding this comment.
// Sometimes the error message might contain clues if status code is not the too many requests error code
58e0fe3 to
915b83a
Compare
…ss-builder reuse that method
915b83a to
db4801f
Compare
…ed a method. However this method does things similar to getBlockTxidsByTransactionHash, so this last calls getBlockInfoByTransactionHash and obtains blockTxIds
No description provided.