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 mempool from '../mempool';
 | 
			
		||||
import { TransactionExtended } from '../../mempool.interfaces';
 | 
			
		||||
import transactionUtils from '../transaction-utils';
 | 
			
		||||
 | 
			
		||||
class BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
  private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
 | 
			
		||||
@ -63,9 +64,16 @@ class BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
    return Promise.resolve([]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getTransactionHex(txId: string): Promise<string> {
 | 
			
		||||
    return this.$getRawTransaction(txId, true)
 | 
			
		||||
      .then((tx) => tx.hex || '');
 | 
			
		||||
  async $getTransactionHex(txId: string): Promise<string> {
 | 
			
		||||
    const txInMempool = mempool.getMempool()[txId];
 | 
			
		||||
    if (txInMempool && txInMempool.hex) {
 | 
			
		||||
      return txInMempool.hex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.bitcoindClient.getRawTransaction(txId, true)
 | 
			
		||||
      .then((transaction: IBitcoinApi.Transaction) => {
 | 
			
		||||
        return transaction.hex;
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getBlockHeightTip(): Promise<number> {
 | 
			
		||||
@ -209,7 +217,7 @@ class BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
        scriptpubkey: vout.scriptPubKey.hex,
 | 
			
		||||
        scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.address ? vout.scriptPubKey.address
 | 
			
		||||
          : 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),
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
@ -219,7 +227,7 @@ class BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
        is_coinbase: !!vin.coinbase,
 | 
			
		||||
        prevout: null,
 | 
			
		||||
        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,
 | 
			
		||||
        txid: vin.txid || '',
 | 
			
		||||
        vout: vin.vout || 0,
 | 
			
		||||
@ -291,7 +299,7 @@ class BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
      }
 | 
			
		||||
      const innerTx = await this.$getRawTransaction(vin.txid, false, false);
 | 
			
		||||
      vin.prevout = innerTx.vout[vin.vout];
 | 
			
		||||
      this.addInnerScriptsToVin(vin);
 | 
			
		||||
      transactionUtils.addInnerScriptsToVin(vin);
 | 
			
		||||
    }
 | 
			
		||||
    return transaction;
 | 
			
		||||
  }
 | 
			
		||||
@ -330,7 +338,7 @@ class BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
      }
 | 
			
		||||
      const innerTx = await this.$getRawTransaction(transaction.vin[i].txid, false, false);
 | 
			
		||||
      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;
 | 
			
		||||
    }
 | 
			
		||||
    if (lazyPrevouts && transaction.vin.length > 12) {
 | 
			
		||||
@ -342,122 +350,6 @@ class BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
@ -149,8 +149,8 @@ class Mempool {
 | 
			
		||||
        logger.err('failed to fetch bulk mempool transactions from esplora');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return newTransactions;
 | 
			
		||||
    logger.info(`Done inserting loaded mempool transactions into local cache`);
 | 
			
		||||
    return newTransactions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $updateMemPoolInfo() {
 | 
			
		||||
@ -219,7 +219,11 @@ class Mempool {
 | 
			
		||||
      logger.info(`Missing ${transactions.length - currentMempoolSize} mempool transactions, attempting to reload in bulk from esplora`);
 | 
			
		||||
      try {
 | 
			
		||||
        newTransactions = await this.$reloadMempool(transactions.length);
 | 
			
		||||
        redisCache.$addTransactions(newTransactions);
 | 
			
		||||
        if (config.REDIS.ENABLED) {
 | 
			
		||||
          for (const tx of newTransactions) {
 | 
			
		||||
            await redisCache.$addTransaction(tx);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        loaded = true;
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        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 { BlockExtended, BlockSummary, MempoolTransactionExtended } from '../mempool.interfaces';
 | 
			
		||||
import rbfCache from './rbf-cache';
 | 
			
		||||
import transactionUtils from './transaction-utils';
 | 
			
		||||
 | 
			
		||||
enum NetworkDB {
 | 
			
		||||
  mainnet = 0,
 | 
			
		||||
@ -20,7 +21,7 @@ class RedisCache {
 | 
			
		||||
  private schemaVersion = 1;
 | 
			
		||||
 | 
			
		||||
  private cacheQueue: MempoolTransactionExtended[] = [];
 | 
			
		||||
  private txFlushLimit: number = 1000;
 | 
			
		||||
  private txFlushLimit: number = 10000;
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    if (config.REDIS.ENABLED) {
 | 
			
		||||
@ -81,7 +82,7 @@ class RedisCache {
 | 
			
		||||
 | 
			
		||||
  async $addTransaction(tx: MempoolTransactionExtended) {
 | 
			
		||||
    this.cacheQueue.push(tx);
 | 
			
		||||
    if (this.cacheQueue.length > this.txFlushLimit) {
 | 
			
		||||
    if (this.cacheQueue.length >= this.txFlushLimit) {
 | 
			
		||||
      await this.$flushTransactions();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -89,15 +90,28 @@ class RedisCache {
 | 
			
		||||
  async $flushTransactions() {
 | 
			
		||||
    const success = await this.$addTransactions(this.cacheQueue);
 | 
			
		||||
    if (success) {
 | 
			
		||||
      logger.info(`Flushed ${this.cacheQueue.length} transactions to Redis cache`);
 | 
			
		||||
      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 {
 | 
			
		||||
      await this.$ensureConnected();
 | 
			
		||||
      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;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
@ -201,6 +215,7 @@ class RedisCache {
 | 
			
		||||
    const loadedBlockSummaries = await this.$getBlockSummaries();
 | 
			
		||||
    // Load mempool
 | 
			
		||||
    const loadedMempool = await this.$getMempool();
 | 
			
		||||
    this.inflateLoadedTxs(loadedMempool);
 | 
			
		||||
    // Load rbf data
 | 
			
		||||
    const rbfTxs = await this.$getRbfEntries('tx');
 | 
			
		||||
    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();
 | 
			
		||||
 | 
			
		||||
@ -188,6 +188,122 @@ class TransactionUtils {
 | 
			
		||||
      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();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user