Merge pull request #5920 from mempool/natsoni/fix-the-filters

Crop large OP_RETURN data in tx details section
This commit is contained in:
mononaut
2025-06-23 11:08:09 +08:00
committed by GitHub
3 changed files with 80 additions and 8 deletions

View File

@@ -327,7 +327,7 @@
@if (vout.isRunestone) {
<button (click)="toggleOrdData(tx.txid, 'vout', vindex)" type="button" class="btn btn-sm badge badge-ord">Runestone</button>
} @else {
<a placement="bottom" [ngbTooltip]="vout.scriptpubkey_asm | hex2ascii"><span *ngIf="vout.scriptpubkey_asm !== 'OP_RETURN'" class="badge badge-secondary scriptmessage">{{ vout.scriptpubkey_asm | hex2ascii }}</span></a>
<a placement="bottom" [ngbTooltip]="vout.scriptpubkey_asm | slice:0:200 | hex2ascii"><span *ngIf="vout.scriptpubkey_asm !== 'OP_RETURN'" class="badge badge-secondary scriptmessage">{{ vout.scriptpubkey_asm | slice:0:200 | hex2ascii }}</span></a>
}
</ng-template>
<ng-template #otherPubkeyType>{{ vout.scriptpubkey_type | scriptpubkeyType }}</ng-template>
@@ -384,15 +384,50 @@
<tbody>
<tr>
<td i18n="transactions-list.scriptpubkey.asm|ScriptPubKey (ASM)">ScriptPubKey (ASM)</td>
<td style="text-align: left;" [innerHTML]="vout.scriptpubkey_asm | asmStyler"></td>
<td style="text-align: left;">
<app-asm [asm]="vout.scriptpubkey_asm" [crop]="showFullScriptPubkeyAsm[vindex] ? 0 : 1000"></app-asm>
<div *ngIf="vout.scriptpubkey_asm.length > 1000" style="display: flex;">
<span *ngIf="!showFullScriptPubkeyAsm[vindex]">...</span>
<label class="btn btn-sm btn-primary mt-2" (click)="toggleShowFullScriptPubkeyAsm(vindex)" style="margin-left: auto;">
<span *ngIf="!showFullScriptPubkeyAsm[vindex]" i18n="show-all">Show all</span>
<span *ngIf="showFullScriptPubkeyAsm[vindex]" i18n="show-less">Show less</span>
</label>
</div>
</td>
</tr>
<tr>
<td i18n="transactions-list.scriptpubkey.hex|ScriptPubKey (HEX)">ScriptPubKey (HEX)</td>
<td style="text-align: left;">{{ vout.scriptpubkey }}</td>
<td style="text-align: left;">
@if (!showFullScriptPubkeyHex[vindex]) {
{{ vout.scriptpubkey | slice:0:1000 }}
} @else {
{{ vout.scriptpubkey }}
}
<div *ngIf="vout.scriptpubkey.length > 1000" style="display: flex;">
<span *ngIf="!showFullScriptPubkeyHex[vindex]">...</span>
<label class="btn btn-sm btn-primary mt-2" (click)="toggleShowFullScriptPubkeyHex(vindex)" style="margin-left: auto;">
<span *ngIf="!showFullScriptPubkeyHex[vindex]" i18n="show-all">Show all</span>
<span *ngIf="showFullScriptPubkeyHex[vindex]" i18n="show-less">Show less</span>
</label>
</div>
</td>
</tr>
<tr *ngIf="vout.scriptpubkey_type == 'op_return'">
<td>OP_RETURN <span>data</span></td>
<td style="text-align: left;">{{ vout.scriptpubkey_asm | hex2ascii }}</td>
<td style="text-align: left; word-break: break-word;">
@if (!showFullOpReturnData[vindex]) {
{{ (vout.scriptpubkey_asm | hex2ascii | slice:0:1000) }}
} @else {
{{ vout.scriptpubkey_asm | hex2ascii }}
}
<div *ngIf="(vout.scriptpubkey_asm | hex2ascii).length > 1000" style="display: flex;">
<span *ngIf="!showFullOpReturnData[vindex]">...</span>
<label class="btn btn-sm btn-primary mt-2" (click)="toggleShowFullOpReturnData(vindex)" style="margin-left: auto;">
<span *ngIf="!showFullOpReturnData[vindex]" i18n="show-all">Show all</span>
<span *ngIf="showFullOpReturnData[vindex]" i18n="show-less">Show less</span>
</label>
</div>
</td>
</tr>
<tr *ngIf="vout.scriptpubkey_type">
<td i18n="transactions-list.vout.scriptpubkey-type">Type</td>

View File

@@ -62,6 +62,9 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
outputRowLimit: number = 12;
showFullScript: { [vinIndex: number]: boolean } = {};
showFullWitness: { [vinIndex: number]: { [witnessIndex: number]: boolean } } = {};
showFullScriptPubkeyAsm: { [voutIndex: number]: boolean } = {};
showFullScriptPubkeyHex: { [voutIndex: number]: boolean } = {};
showFullOpReturnData: { [voutIndex: number]: boolean } = {};
showOrdData: { [key: string]: { show: boolean; inscriptions?: Inscription[]; runestone?: Runestone, runeInfo?: { [id: string]: { etching: Etching; txid: string; } }; } } = {};
similarityMatches: Map<string, Map<string, { score: number, match: AddressMatch, group: number }>> = new Map();
@@ -516,6 +519,18 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
this.showFullWitness[vinIndex][witnessIndex] = !this.showFullWitness[vinIndex][witnessIndex];
}
toggleShowFullScriptPubkeyAsm(voutIndex: number): void {
this.showFullScriptPubkeyAsm[voutIndex] = !this.showFullScriptPubkeyAsm[voutIndex];
}
toggleShowFullScriptPubkeyHex(voutIndex: number): void {
this.showFullScriptPubkeyHex[voutIndex] = !this.showFullScriptPubkeyHex[voutIndex];
}
toggleShowFullOpReturnData(voutIndex: number): void {
this.showFullOpReturnData[voutIndex] = !this.showFullOpReturnData[voutIndex];
}
toggleOrdData(txid: string, type: 'vin' | 'vout', index: number) {
const tx = this.transactions.find((tx) => tx.txid === txid);
if (!tx) {

View File

@@ -28,7 +28,7 @@ export class AsmComponent {
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['asm']) {
if (changes['asm'] || changes['crop']) {
this.parseASM();
}
}
@@ -36,14 +36,35 @@ export class AsmComponent {
parseASM(): void {
let instructions = this.asm.split('OP_');
// trim instructions to a whole number of instructions with at most `crop` characters total
if (this.crop) {
if (this.crop && this.asm.length > this.crop) {
let chars = 0;
for (let i = 0; i < instructions.length; i++) {
chars += instructions[i].length + 3;
if (chars > this.crop) {
if (chars + instructions[i].length + 3 > this.crop) {
let croppedInstruction = instructions[i];
instructions = instructions.slice(0, i);
// add cropped instruction
let remainingChars = this.crop - chars;
let parts = croppedInstruction.split(' ');
// only render this instruction if there is space for the instruction name and a few args
if (remainingChars > parts[0].length + 10) {
remainingChars -= parts[0].length + 1;
for (let j = 1; j < parts.length; j++) {
const arg = parts[j];
if (remainingChars >= arg.length) {
remainingChars -= arg.length + 1;
} else {
// crop this argument
parts[j] = arg.slice(0, remainingChars);
// and remove all following arguments
parts = parts.slice(0, j + 1);
break;
}
}
instructions.push(`${parts.join(' ')}`);
}
break;
}
chars += instructions[i].length + 3;
}
}
this.instructions = instructions.filter(instruction => instruction.trim() !== '').map(instruction => {
@@ -82,6 +103,7 @@ export class AsmComponent {
['ELSE', 'control'],
['ENDIF', 'control'],
['VERIFY', 'control'],
['RETURN', 'control'],
...Array.from({length: 70}, (_, i) => [`RETURN_${i + 186}`, 'control']),
// Stack