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