strip non-essential data from redis cache txs
This commit is contained in:
		
							parent
							
								
									6ac58f2da7
								
							
						
					
					
						commit
						a393f42b5e
					
				| @ -5,6 +5,7 @@ import { IEsploraApi } from './esplora-api.interface'; | |||||||
| import blocks from '../blocks'; | import blocks from '../blocks'; | ||||||
| import mempool from '../mempool'; | import mempool from '../mempool'; | ||||||
| import { TransactionExtended } from '../../mempool.interfaces'; | import { TransactionExtended } from '../../mempool.interfaces'; | ||||||
|  | import transactionUtils from '../transaction-utils'; | ||||||
| 
 | 
 | ||||||
| class BitcoinApi implements AbstractBitcoinApi { | class BitcoinApi implements AbstractBitcoinApi { | ||||||
|   private rawMempoolCache: IBitcoinApi.RawMempool | null = null; |   private rawMempoolCache: IBitcoinApi.RawMempool | null = null; | ||||||
| @ -63,9 +64,16 @@ class BitcoinApi implements AbstractBitcoinApi { | |||||||
|     return Promise.resolve([]); |     return Promise.resolve([]); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getTransactionHex(txId: string): Promise<string> { |   async $getTransactionHex(txId: string): Promise<string> { | ||||||
|     return this.$getRawTransaction(txId, true) |     const txInMempool = mempool.getMempool()[txId]; | ||||||
|       .then((tx) => tx.hex || ''); |     if (txInMempool && txInMempool.hex) { | ||||||
|  |       return txInMempool.hex; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return this.bitcoindClient.getRawTransaction(txId, true) | ||||||
|  |       .then((transaction: IBitcoinApi.Transaction) => { | ||||||
|  |         return transaction.hex; | ||||||
|  |       }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getBlockHeightTip(): Promise<number> { |   $getBlockHeightTip(): Promise<number> { | ||||||
| @ -209,7 +217,7 @@ class BitcoinApi implements AbstractBitcoinApi { | |||||||
|         scriptpubkey: vout.scriptPubKey.hex, |         scriptpubkey: vout.scriptPubKey.hex, | ||||||
|         scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.address ? vout.scriptPubKey.address |         scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.address ? vout.scriptPubKey.address | ||||||
|           : vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : '', |           : vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : '', | ||||||
|         scriptpubkey_asm: vout.scriptPubKey.asm ? this.convertScriptSigAsm(vout.scriptPubKey.hex) : '', |         scriptpubkey_asm: vout.scriptPubKey.asm ? transactionUtils.convertScriptSigAsm(vout.scriptPubKey.hex) : '', | ||||||
|         scriptpubkey_type: this.translateScriptPubKeyType(vout.scriptPubKey.type), |         scriptpubkey_type: this.translateScriptPubKeyType(vout.scriptPubKey.type), | ||||||
|       }; |       }; | ||||||
|     }); |     }); | ||||||
| @ -219,7 +227,7 @@ class BitcoinApi implements AbstractBitcoinApi { | |||||||
|         is_coinbase: !!vin.coinbase, |         is_coinbase: !!vin.coinbase, | ||||||
|         prevout: null, |         prevout: null, | ||||||
|         scriptsig: vin.scriptSig && vin.scriptSig.hex || vin.coinbase || '', |         scriptsig: vin.scriptSig && vin.scriptSig.hex || vin.coinbase || '', | ||||||
|         scriptsig_asm: vin.scriptSig && this.convertScriptSigAsm(vin.scriptSig.hex) || '', |         scriptsig_asm: vin.scriptSig && transactionUtils.convertScriptSigAsm(vin.scriptSig.hex) || '', | ||||||
|         sequence: vin.sequence, |         sequence: vin.sequence, | ||||||
|         txid: vin.txid || '', |         txid: vin.txid || '', | ||||||
|         vout: vin.vout || 0, |         vout: vin.vout || 0, | ||||||
| @ -291,7 +299,7 @@ class BitcoinApi implements AbstractBitcoinApi { | |||||||
|       } |       } | ||||||
|       const innerTx = await this.$getRawTransaction(vin.txid, false, false); |       const innerTx = await this.$getRawTransaction(vin.txid, false, false); | ||||||
|       vin.prevout = innerTx.vout[vin.vout]; |       vin.prevout = innerTx.vout[vin.vout]; | ||||||
|       this.addInnerScriptsToVin(vin); |       transactionUtils.addInnerScriptsToVin(vin); | ||||||
|     } |     } | ||||||
|     return transaction; |     return transaction; | ||||||
|   } |   } | ||||||
| @ -330,7 +338,7 @@ class BitcoinApi implements AbstractBitcoinApi { | |||||||
|       } |       } | ||||||
|       const innerTx = await this.$getRawTransaction(transaction.vin[i].txid, false, false); |       const innerTx = await this.$getRawTransaction(transaction.vin[i].txid, false, false); | ||||||
|       transaction.vin[i].prevout = innerTx.vout[transaction.vin[i].vout]; |       transaction.vin[i].prevout = innerTx.vout[transaction.vin[i].vout]; | ||||||
|       this.addInnerScriptsToVin(transaction.vin[i]); |       transactionUtils.addInnerScriptsToVin(transaction.vin[i]); | ||||||
|       totalIn += innerTx.vout[transaction.vin[i].vout].value; |       totalIn += innerTx.vout[transaction.vin[i].vout].value; | ||||||
|     } |     } | ||||||
|     if (lazyPrevouts && transaction.vin.length > 12) { |     if (lazyPrevouts && transaction.vin.length > 12) { | ||||||
| @ -342,122 +350,6 @@ class BitcoinApi implements AbstractBitcoinApi { | |||||||
|     return transaction; |     return transaction; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private convertScriptSigAsm(hex: string): string { |  | ||||||
|     const buf = Buffer.from(hex, 'hex'); |  | ||||||
| 
 |  | ||||||
|     const b: string[] = []; |  | ||||||
| 
 |  | ||||||
|     let i = 0; |  | ||||||
|     while (i < buf.length) { |  | ||||||
|       const op = buf[i]; |  | ||||||
|       if (op >= 0x01 && op <= 0x4e) { |  | ||||||
|         i++; |  | ||||||
|         let push: number; |  | ||||||
|         if (op === 0x4c) { |  | ||||||
|           push = buf.readUInt8(i); |  | ||||||
|           b.push('OP_PUSHDATA1'); |  | ||||||
|           i += 1; |  | ||||||
|         } else if (op === 0x4d) { |  | ||||||
|           push = buf.readUInt16LE(i); |  | ||||||
|           b.push('OP_PUSHDATA2'); |  | ||||||
|           i += 2; |  | ||||||
|         } else if (op === 0x4e) { |  | ||||||
|           push = buf.readUInt32LE(i); |  | ||||||
|           b.push('OP_PUSHDATA4'); |  | ||||||
|           i += 4; |  | ||||||
|         } else { |  | ||||||
|           push = op; |  | ||||||
|           b.push('OP_PUSHBYTES_' + push); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const data = buf.slice(i, i + push); |  | ||||||
|         if (data.length !== push) { |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         b.push(data.toString('hex')); |  | ||||||
|         i += data.length; |  | ||||||
|       } else { |  | ||||||
|         if (op === 0x00) { |  | ||||||
|           b.push('OP_0'); |  | ||||||
|         } else if (op === 0x4f) { |  | ||||||
|           b.push('OP_PUSHNUM_NEG1'); |  | ||||||
|         } else if (op === 0xb1) { |  | ||||||
|           b.push('OP_CLTV'); |  | ||||||
|         } else if (op === 0xb2) { |  | ||||||
|           b.push('OP_CSV'); |  | ||||||
|         } else if (op === 0xba) { |  | ||||||
|           b.push('OP_CHECKSIGADD'); |  | ||||||
|         } else { |  | ||||||
|           const opcode = bitcoinjs.script.toASM([ op ]); |  | ||||||
|           if (opcode && op < 0xfd) { |  | ||||||
|             if (/^OP_(\d+)$/.test(opcode)) { |  | ||||||
|               b.push(opcode.replace(/^OP_(\d+)$/, 'OP_PUSHNUM_$1')); |  | ||||||
|             } else { |  | ||||||
|               b.push(opcode); |  | ||||||
|             } |  | ||||||
|           } else { |  | ||||||
|             b.push('OP_RETURN_' + op); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|         i += 1; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return b.join(' '); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private addInnerScriptsToVin(vin: IEsploraApi.Vin): void { |  | ||||||
|     if (!vin.prevout) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (vin.prevout.scriptpubkey_type === 'p2sh') { |  | ||||||
|       const redeemScript = vin.scriptsig_asm.split(' ').reverse()[0]; |  | ||||||
|       vin.inner_redeemscript_asm = this.convertScriptSigAsm(redeemScript); |  | ||||||
|       if (vin.witness && vin.witness.length > 2) { |  | ||||||
|         const witnessScript = vin.witness[vin.witness.length - 1]; |  | ||||||
|         vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (vin.prevout.scriptpubkey_type === 'v0_p2wsh' && vin.witness) { |  | ||||||
|       const witnessScript = vin.witness[vin.witness.length - 1]; |  | ||||||
|       vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (vin.prevout.scriptpubkey_type === 'v1_p2tr' && vin.witness) { |  | ||||||
|       const witnessScript = this.witnessToP2TRScript(vin.witness); |  | ||||||
|       if (witnessScript !== null) { |  | ||||||
|         vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |  | ||||||
|    * This function must only be called when we know the witness we are parsing |  | ||||||
|    * is a taproot witness. |  | ||||||
|    * @param witness An array of hex strings that represents the witness stack of |  | ||||||
|    *                the input. |  | ||||||
|    * @returns null if the witness is not a script spend, and the hex string of |  | ||||||
|    *          the script item if it is a script spend. |  | ||||||
|    */ |  | ||||||
|   private witnessToP2TRScript(witness: string[]): string | null { |  | ||||||
|     if (witness.length < 2) return null; |  | ||||||
|     // Note: see BIP341 for parsing details of witness stack
 |  | ||||||
| 
 |  | ||||||
|     // If there are at least two witness elements, and the first byte of the
 |  | ||||||
|     // last element is 0x50, this last element is called annex a and
 |  | ||||||
|     // is removed from the witness stack.
 |  | ||||||
|     const hasAnnex = witness[witness.length - 1].substring(0, 2) === '50'; |  | ||||||
|     // If there are at least two witness elements left, script path spending is used.
 |  | ||||||
|     // Call the second-to-last stack element s, the script.
 |  | ||||||
|     // (Note: this phrasing from BIP341 assumes we've *removed* the annex from the stack)
 |  | ||||||
|     if (hasAnnex && witness.length < 3) return null; |  | ||||||
|     const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2; |  | ||||||
|     return witness[positionOfScript]; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default BitcoinApi; | export default BitcoinApi; | ||||||
|  | |||||||
| @ -149,8 +149,8 @@ class Mempool { | |||||||
|         logger.err('failed to fetch bulk mempool transactions from esplora'); |         logger.err('failed to fetch bulk mempool transactions from esplora'); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return newTransactions; |  | ||||||
|     logger.info(`Done inserting loaded mempool transactions into local cache`); |     logger.info(`Done inserting loaded mempool transactions into local cache`); | ||||||
|  |     return newTransactions; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async $updateMemPoolInfo() { |   public async $updateMemPoolInfo() { | ||||||
| @ -219,7 +219,11 @@ class Mempool { | |||||||
|       logger.info(`Missing ${transactions.length - currentMempoolSize} mempool transactions, attempting to reload in bulk from esplora`); |       logger.info(`Missing ${transactions.length - currentMempoolSize} mempool transactions, attempting to reload in bulk from esplora`); | ||||||
|       try { |       try { | ||||||
|         newTransactions = await this.$reloadMempool(transactions.length); |         newTransactions = await this.$reloadMempool(transactions.length); | ||||||
|         redisCache.$addTransactions(newTransactions); |         if (config.REDIS.ENABLED) { | ||||||
|  |           for (const tx of newTransactions) { | ||||||
|  |             await redisCache.$addTransaction(tx); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         loaded = true; |         loaded = true; | ||||||
|       } catch (e) { |       } catch (e) { | ||||||
|         logger.err('failed to load mempool in bulk from esplora, falling back to fetching individual transactions'); |         logger.err('failed to load mempool in bulk from esplora, falling back to fetching individual transactions'); | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import logger from '../logger'; | |||||||
| import config from '../config'; | import config from '../config'; | ||||||
| import { BlockExtended, BlockSummary, MempoolTransactionExtended } from '../mempool.interfaces'; | import { BlockExtended, BlockSummary, MempoolTransactionExtended } from '../mempool.interfaces'; | ||||||
| import rbfCache from './rbf-cache'; | import rbfCache from './rbf-cache'; | ||||||
|  | import transactionUtils from './transaction-utils'; | ||||||
| 
 | 
 | ||||||
| enum NetworkDB { | enum NetworkDB { | ||||||
|   mainnet = 0, |   mainnet = 0, | ||||||
| @ -20,7 +21,7 @@ class RedisCache { | |||||||
|   private schemaVersion = 1; |   private schemaVersion = 1; | ||||||
| 
 | 
 | ||||||
|   private cacheQueue: MempoolTransactionExtended[] = []; |   private cacheQueue: MempoolTransactionExtended[] = []; | ||||||
|   private txFlushLimit: number = 1000; |   private txFlushLimit: number = 10000; | ||||||
| 
 | 
 | ||||||
|   constructor() { |   constructor() { | ||||||
|     if (config.REDIS.ENABLED) { |     if (config.REDIS.ENABLED) { | ||||||
| @ -81,7 +82,7 @@ class RedisCache { | |||||||
| 
 | 
 | ||||||
|   async $addTransaction(tx: MempoolTransactionExtended) { |   async $addTransaction(tx: MempoolTransactionExtended) { | ||||||
|     this.cacheQueue.push(tx); |     this.cacheQueue.push(tx); | ||||||
|     if (this.cacheQueue.length > this.txFlushLimit) { |     if (this.cacheQueue.length >= this.txFlushLimit) { | ||||||
|       await this.$flushTransactions(); |       await this.$flushTransactions(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -89,15 +90,28 @@ class RedisCache { | |||||||
|   async $flushTransactions() { |   async $flushTransactions() { | ||||||
|     const success = await this.$addTransactions(this.cacheQueue); |     const success = await this.$addTransactions(this.cacheQueue); | ||||||
|     if (success) { |     if (success) { | ||||||
|  |       logger.info(`Flushed ${this.cacheQueue.length} transactions to Redis cache`); | ||||||
|       this.cacheQueue = []; |       this.cacheQueue = []; | ||||||
|  |     } else { | ||||||
|  |       logger.err(`Failed to flush ${this.cacheQueue.length} transactions to Redis cache`); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async $addTransactions(newTransactions: MempoolTransactionExtended[]): Promise<boolean> { |   private async $addTransactions(newTransactions: MempoolTransactionExtended[]): Promise<boolean> { | ||||||
|     try { |     try { | ||||||
|       await this.$ensureConnected(); |       await this.$ensureConnected(); | ||||||
|       await Promise.all(newTransactions.map(tx => { |       await Promise.all(newTransactions.map(tx => { | ||||||
|         return this.client.json.set(`mempool:${tx.txid.slice(0,1)}`, tx.txid, tx); |         const minified: any = { ...tx }; | ||||||
|  |         delete minified.hex; | ||||||
|  |         for (const vin of minified.vin) { | ||||||
|  |           delete vin.inner_redeemscript_asm; | ||||||
|  |           delete vin.inner_witnessscript_asm; | ||||||
|  |           delete vin.scriptsig_asm; | ||||||
|  |         } | ||||||
|  |         for (const vout of minified.vout) { | ||||||
|  |           delete vout.scriptpubkey_asm; | ||||||
|  |         } | ||||||
|  |         return this.client.json.set(`mempool:${tx.txid.slice(0,1)}`, tx.txid, minified); | ||||||
|       })); |       })); | ||||||
|       return true; |       return true; | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
| @ -201,6 +215,7 @@ class RedisCache { | |||||||
|     const loadedBlockSummaries = await this.$getBlockSummaries(); |     const loadedBlockSummaries = await this.$getBlockSummaries(); | ||||||
|     // Load mempool
 |     // Load mempool
 | ||||||
|     const loadedMempool = await this.$getMempool(); |     const loadedMempool = await this.$getMempool(); | ||||||
|  |     this.inflateLoadedTxs(loadedMempool); | ||||||
|     // Load rbf data
 |     // Load rbf data
 | ||||||
|     const rbfTxs = await this.$getRbfEntries('tx'); |     const rbfTxs = await this.$getRbfEntries('tx'); | ||||||
|     const rbfTrees = await this.$getRbfEntries('tree'); |     const rbfTrees = await this.$getRbfEntries('tree'); | ||||||
| @ -217,6 +232,22 @@ class RedisCache { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private inflateLoadedTxs(mempool: { [txid: string]: MempoolTransactionExtended }) { | ||||||
|  |     for (const tx of Object.values(mempool)) { | ||||||
|  |       for (const vin of tx.vin) { | ||||||
|  |         if (vin.scriptsig) { | ||||||
|  |           vin.scriptsig_asm = transactionUtils.convertScriptSigAsm(vin.scriptsig); | ||||||
|  |           transactionUtils.addInnerScriptsToVin(vin); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       for (const vout of tx.vout) { | ||||||
|  |         if (vout.scriptpubkey) { | ||||||
|  |           vout.scriptpubkey_asm = transactionUtils.convertScriptSigAsm(vout.scriptpubkey); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default new RedisCache(); | export default new RedisCache(); | ||||||
|  | |||||||
| @ -188,6 +188,122 @@ class TransactionUtils { | |||||||
|       16 |       16 | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   public addInnerScriptsToVin(vin: IEsploraApi.Vin): void { | ||||||
|  |     if (!vin.prevout) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (vin.prevout.scriptpubkey_type === 'p2sh') { | ||||||
|  |       const redeemScript = vin.scriptsig_asm.split(' ').reverse()[0]; | ||||||
|  |       vin.inner_redeemscript_asm = this.convertScriptSigAsm(redeemScript); | ||||||
|  |       if (vin.witness && vin.witness.length > 2) { | ||||||
|  |         const witnessScript = vin.witness[vin.witness.length - 1]; | ||||||
|  |         vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (vin.prevout.scriptpubkey_type === 'v0_p2wsh' && vin.witness) { | ||||||
|  |       const witnessScript = vin.witness[vin.witness.length - 1]; | ||||||
|  |       vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (vin.prevout.scriptpubkey_type === 'v1_p2tr' && vin.witness) { | ||||||
|  |       const witnessScript = this.witnessToP2TRScript(vin.witness); | ||||||
|  |       if (witnessScript !== null) { | ||||||
|  |         vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public convertScriptSigAsm(hex: string): string { | ||||||
|  |     const buf = Buffer.from(hex, 'hex'); | ||||||
|  | 
 | ||||||
|  |     const b: string[] = []; | ||||||
|  | 
 | ||||||
|  |     let i = 0; | ||||||
|  |     while (i < buf.length) { | ||||||
|  |       const op = buf[i]; | ||||||
|  |       if (op >= 0x01 && op <= 0x4e) { | ||||||
|  |         i++; | ||||||
|  |         let push: number; | ||||||
|  |         if (op === 0x4c) { | ||||||
|  |           push = buf.readUInt8(i); | ||||||
|  |           b.push('OP_PUSHDATA1'); | ||||||
|  |           i += 1; | ||||||
|  |         } else if (op === 0x4d) { | ||||||
|  |           push = buf.readUInt16LE(i); | ||||||
|  |           b.push('OP_PUSHDATA2'); | ||||||
|  |           i += 2; | ||||||
|  |         } else if (op === 0x4e) { | ||||||
|  |           push = buf.readUInt32LE(i); | ||||||
|  |           b.push('OP_PUSHDATA4'); | ||||||
|  |           i += 4; | ||||||
|  |         } else { | ||||||
|  |           push = op; | ||||||
|  |           b.push('OP_PUSHBYTES_' + push); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const data = buf.slice(i, i + push); | ||||||
|  |         if (data.length !== push) { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         b.push(data.toString('hex')); | ||||||
|  |         i += data.length; | ||||||
|  |       } else { | ||||||
|  |         if (op === 0x00) { | ||||||
|  |           b.push('OP_0'); | ||||||
|  |         } else if (op === 0x4f) { | ||||||
|  |           b.push('OP_PUSHNUM_NEG1'); | ||||||
|  |         } else if (op === 0xb1) { | ||||||
|  |           b.push('OP_CLTV'); | ||||||
|  |         } else if (op === 0xb2) { | ||||||
|  |           b.push('OP_CSV'); | ||||||
|  |         } else if (op === 0xba) { | ||||||
|  |           b.push('OP_CHECKSIGADD'); | ||||||
|  |         } else { | ||||||
|  |           const opcode = bitcoinjs.script.toASM([ op ]); | ||||||
|  |           if (opcode && op < 0xfd) { | ||||||
|  |             if (/^OP_(\d+)$/.test(opcode)) { | ||||||
|  |               b.push(opcode.replace(/^OP_(\d+)$/, 'OP_PUSHNUM_$1')); | ||||||
|  |             } else { | ||||||
|  |               b.push(opcode); | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             b.push('OP_RETURN_' + op); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         i += 1; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return b.join(' '); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * This function must only be called when we know the witness we are parsing | ||||||
|  |    * is a taproot witness. | ||||||
|  |    * @param witness An array of hex strings that represents the witness stack of | ||||||
|  |    *                the input. | ||||||
|  |    * @returns null if the witness is not a script spend, and the hex string of | ||||||
|  |    *          the script item if it is a script spend. | ||||||
|  |    */ | ||||||
|  |   public witnessToP2TRScript(witness: string[]): string | null { | ||||||
|  |     if (witness.length < 2) return null; | ||||||
|  |     // Note: see BIP341 for parsing details of witness stack
 | ||||||
|  | 
 | ||||||
|  |     // If there are at least two witness elements, and the first byte of the
 | ||||||
|  |     // last element is 0x50, this last element is called annex a and
 | ||||||
|  |     // is removed from the witness stack.
 | ||||||
|  |     const hasAnnex = witness[witness.length - 1].substring(0, 2) === '50'; | ||||||
|  |     // If there are at least two witness elements left, script path spending is used.
 | ||||||
|  |     // Call the second-to-last stack element s, the script.
 | ||||||
|  |     // (Note: this phrasing from BIP341 assumes we've *removed* the annex from the stack)
 | ||||||
|  |     if (hasAnnex && witness.length < 3) return null; | ||||||
|  |     const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2; | ||||||
|  |     return witness[positionOfScript]; | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default new TransactionUtils(); | export default new TransactionUtils(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user