Display basic mining info on transaction page
This commit is contained in:
		
							parent
							
								
									29bbb922c5
								
							
						
					
					
						commit
						c17b77fe31
					
				@ -70,6 +70,25 @@
 | 
				
			|||||||
                    <app-tx-features [tx]="tx"></app-tx-features>
 | 
					                    <app-tx-features [tx]="tx"></app-tx-features>
 | 
				
			||||||
                  </td>
 | 
					                  </td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
 | 
					                <tr *ngIf="network === ''">
 | 
				
			||||||
 | 
					                  <td class="td-width" i18n="transaction.mining">Mining</td>
 | 
				
			||||||
 | 
					                  <td *ngIf="pool" class="wrap-cell">
 | 
				
			||||||
 | 
					                    <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge mr-1"
 | 
				
			||||||
 | 
					                      [class]="pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
 | 
				
			||||||
 | 
					                      {{ pool.name }}
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <ng-container *ngIf="auditStatus">
 | 
				
			||||||
 | 
					                      <span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span>
 | 
				
			||||||
 | 
					                      <ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template>
 | 
				
			||||||
 | 
					                      <ng-template #notSeen><span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template>
 | 
				
			||||||
 | 
					                      <span *ngIf="auditStatus.added" class="badge badge-primary mr-1" i18n-ngbTooltip="Added transaction tooltip" ngbTooltip="This transaction may have been added or prioritized out-of-band" placement="bottom" i18n="tx-features.tag.added|Added">Added</span>
 | 
				
			||||||
 | 
					                      <span *ngIf="auditStatus.conflict" class="badge badge-warning mr-1" i18n-ngbTooltip="Conflict in mempool tooltip" ngbTooltip="This transaction conflicted with another version in our mempool" placement="bottom" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
 | 
				
			||||||
 | 
					                    </ng-container>
 | 
				
			||||||
 | 
					                  </td>
 | 
				
			||||||
 | 
					                  <td *ngIf="!pool">
 | 
				
			||||||
 | 
					                    <span class="skeleton-loader"></span>
 | 
				
			||||||
 | 
					                  </td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
              </tbody>
 | 
					              </tbody>
 | 
				
			||||||
            </table>
 | 
					            </table>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@ -509,7 +528,7 @@
 | 
				
			|||||||
<ng-template #feeTable>
 | 
					<ng-template #feeTable>
 | 
				
			||||||
  <table class="table table-borderless table-striped">
 | 
					  <table class="table table-borderless table-striped">
 | 
				
			||||||
    <tbody>
 | 
					    <tbody>
 | 
				
			||||||
      <tr *ngIf="isMobile && (network === 'liquid' || network === 'liquidtestnet' || !featuresEnabled)"></tr>
 | 
					      <tr *ngIf="isMobile && (network === 'liquid' || network === 'liquidtestnet' || !featuresEnabled || network === '')"></tr>
 | 
				
			||||||
      <tr>
 | 
					      <tr>
 | 
				
			||||||
        <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
 | 
					        <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
 | 
				
			||||||
        <td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="blockConversion" [value]="tx.fee"></app-fiat></span></td>
 | 
					        <td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="blockConversion" [value]="tx.fee"></app-fiat></span></td>
 | 
				
			||||||
 | 
				
			|||||||
@ -149,6 +149,10 @@
 | 
				
			|||||||
		.btn {
 | 
							.btn {
 | 
				
			||||||
			display: block;
 | 
								display: block;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.wrap-cell {
 | 
				
			||||||
 | 
					      white-space: normal;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -8,10 +8,11 @@ import {
 | 
				
			|||||||
  retryWhen,
 | 
					  retryWhen,
 | 
				
			||||||
  delay,
 | 
					  delay,
 | 
				
			||||||
  mergeMap,
 | 
					  mergeMap,
 | 
				
			||||||
  tap
 | 
					  tap,
 | 
				
			||||||
 | 
					  map
 | 
				
			||||||
} from 'rxjs/operators';
 | 
					} from 'rxjs/operators';
 | 
				
			||||||
import { Transaction } from '../../interfaces/electrs.interface';
 | 
					import { Transaction } from '../../interfaces/electrs.interface';
 | 
				
			||||||
import { of, merge, Subscription, Observable, Subject, from, throwError } from 'rxjs';
 | 
					import { of, merge, Subscription, Observable, Subject, from, throwError, combineLatest } from 'rxjs';
 | 
				
			||||||
import { StateService } from '../../services/state.service';
 | 
					import { StateService } from '../../services/state.service';
 | 
				
			||||||
import { CacheService } from '../../services/cache.service';
 | 
					import { CacheService } from '../../services/cache.service';
 | 
				
			||||||
import { WebsocketService } from '../../services/websocket.service';
 | 
					import { WebsocketService } from '../../services/websocket.service';
 | 
				
			||||||
@ -28,6 +29,21 @@ import { isFeatureActive } from '../../bitcoin.utils';
 | 
				
			|||||||
import { ServicesApiServices } from '../../services/services-api.service';
 | 
					import { ServicesApiServices } from '../../services/services-api.service';
 | 
				
			||||||
import { EnterpriseService } from '../../services/enterprise.service';
 | 
					import { EnterpriseService } from '../../services/enterprise.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Pool {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  slug: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AuditStatus {
 | 
				
			||||||
 | 
					  seen: boolean;
 | 
				
			||||||
 | 
					  expected: boolean;
 | 
				
			||||||
 | 
					  added: boolean;
 | 
				
			||||||
 | 
					  delayed?: number;
 | 
				
			||||||
 | 
					  accelerated: boolean;
 | 
				
			||||||
 | 
					  conflict: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-transaction',
 | 
					  selector: 'app-transaction',
 | 
				
			||||||
  templateUrl: './transaction.component.html',
 | 
					  templateUrl: './transaction.component.html',
 | 
				
			||||||
@ -58,6 +74,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
  urlFragmentSubscription: Subscription;
 | 
					  urlFragmentSubscription: Subscription;
 | 
				
			||||||
  mempoolBlocksSubscription: Subscription;
 | 
					  mempoolBlocksSubscription: Subscription;
 | 
				
			||||||
  blocksSubscription: Subscription;
 | 
					  blocksSubscription: Subscription;
 | 
				
			||||||
 | 
					  miningSubscription: Subscription;
 | 
				
			||||||
  fragmentParams: URLSearchParams;
 | 
					  fragmentParams: URLSearchParams;
 | 
				
			||||||
  rbfTransaction: undefined | Transaction;
 | 
					  rbfTransaction: undefined | Transaction;
 | 
				
			||||||
  replaced: boolean = false;
 | 
					  replaced: boolean = false;
 | 
				
			||||||
@ -67,11 +84,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
  accelerationInfo: Acceleration | null = null;
 | 
					  accelerationInfo: Acceleration | null = null;
 | 
				
			||||||
  sigops: number | null;
 | 
					  sigops: number | null;
 | 
				
			||||||
  adjustedVsize: number | null;
 | 
					  adjustedVsize: number | null;
 | 
				
			||||||
 | 
					  pool: Pool | null;
 | 
				
			||||||
 | 
					  auditStatus: AuditStatus | null;
 | 
				
			||||||
  showCpfpDetails = false;
 | 
					  showCpfpDetails = false;
 | 
				
			||||||
  fetchCpfp$ = new Subject<string>();
 | 
					  fetchCpfp$ = new Subject<string>();
 | 
				
			||||||
  fetchRbfHistory$ = new Subject<string>();
 | 
					  fetchRbfHistory$ = new Subject<string>();
 | 
				
			||||||
  fetchCachedTx$ = new Subject<string>();
 | 
					  fetchCachedTx$ = new Subject<string>();
 | 
				
			||||||
  fetchAcceleration$ = new Subject<string>();
 | 
					  fetchAcceleration$ = new Subject<string>();
 | 
				
			||||||
 | 
					  fetchMiningInfo$ = new Subject<{ hash: string, height: number, txid: string }>();
 | 
				
			||||||
  isCached: boolean = false;
 | 
					  isCached: boolean = false;
 | 
				
			||||||
  now = Date.now();
 | 
					  now = Date.now();
 | 
				
			||||||
  da$: Observable<DifficultyAdjustment>;
 | 
					  da$: Observable<DifficultyAdjustment>;
 | 
				
			||||||
@ -100,6 +120,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
  acceleratorAvailable: boolean = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
 | 
					  acceleratorAvailable: boolean = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
 | 
				
			||||||
  showAccelerationSummary = false;
 | 
					  showAccelerationSummary = false;
 | 
				
			||||||
  scrollIntoAccelPreview = false;
 | 
					  scrollIntoAccelPreview = false;
 | 
				
			||||||
 | 
					  auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ViewChild('graphContainer')
 | 
					  @ViewChild('graphContainer')
 | 
				
			||||||
  graphContainer: ElementRef;
 | 
					  graphContainer: ElementRef;
 | 
				
			||||||
@ -266,6 +287,52 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.miningSubscription = this.fetchMiningInfo$.pipe(
 | 
				
			||||||
 | 
					      filter((target) => target.txid === this.txId),
 | 
				
			||||||
 | 
					      tap(() => {
 | 
				
			||||||
 | 
					        this.pool = null;
 | 
				
			||||||
 | 
					        this.auditStatus = null;
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					      switchMap(({ hash, height, txid }) => {
 | 
				
			||||||
 | 
					        const foundBlock = this.cacheService.getCachedBlock(height) || null;
 | 
				
			||||||
 | 
					        const auditAvailable = this.isAuditAvailable(height);
 | 
				
			||||||
 | 
					        return combineLatest([
 | 
				
			||||||
 | 
					          foundBlock ? of(foundBlock.extras.pool) : this.apiService.getBlock$(hash).pipe(
 | 
				
			||||||
 | 
					            map(block => {
 | 
				
			||||||
 | 
					              return block.extras.pool;
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            catchError(() => {
 | 
				
			||||||
 | 
					              return of(null);
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          auditAvailable ? this.apiService.getBlockAudit$(hash).pipe(
 | 
				
			||||||
 | 
					            map(audit => {
 | 
				
			||||||
 | 
					              const isAdded = audit.addedTxs.includes(txid);
 | 
				
			||||||
 | 
					              const isAccelerated = audit.acceleratedTxs.includes(txid);
 | 
				
			||||||
 | 
					              const isConflict = audit.fullrbfTxs.includes(txid);
 | 
				
			||||||
 | 
					              const isExpected = audit.template.some(tx => tx.txid === txid);
 | 
				
			||||||
 | 
					              return {
 | 
				
			||||||
 | 
					                seen: isExpected || !(isAdded || isConflict),
 | 
				
			||||||
 | 
					                expected: isExpected,
 | 
				
			||||||
 | 
					                added: isAdded,
 | 
				
			||||||
 | 
					                conflict: isConflict,
 | 
				
			||||||
 | 
					                accelerated: isAccelerated,
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            catchError(() => {
 | 
				
			||||||
 | 
					              return of(null);
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          ) : of(null)
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					      catchError(() => {
 | 
				
			||||||
 | 
					        return of(null);
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).subscribe(([pool, auditStatus]) => {
 | 
				
			||||||
 | 
					      this.pool = pool;
 | 
				
			||||||
 | 
					      this.auditStatus = auditStatus;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => {
 | 
					    this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => {
 | 
				
			||||||
      this.now = Date.now();
 | 
					      this.now = Date.now();
 | 
				
			||||||
      if (txPosition && txPosition.txid === this.txId && txPosition.position) {
 | 
					      if (txPosition && txPosition.txid === this.txId && txPosition.position) {
 | 
				
			||||||
@ -396,6 +463,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            this.fetchAcceleration$.next(tx.status.block_hash);
 | 
					            this.fetchAcceleration$.next(tx.status.block_hash);
 | 
				
			||||||
 | 
					            this.fetchMiningInfo$.next({ hash: tx.status.block_hash, height: tx.status.block_height, txid: tx.txid });
 | 
				
			||||||
            this.transactionTime = 0;
 | 
					            this.transactionTime = 0;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -453,6 +521,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
          this.audioService.playSound('magic');
 | 
					          this.audioService.playSound('magic');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.fetchAcceleration$.next(block.id);
 | 
					        this.fetchAcceleration$.next(block.id);
 | 
				
			||||||
 | 
					        this.fetchMiningInfo$.next({ hash: block.id, height: block.height, txid: this.tx.txid });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -606,6 +675,29 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
    this.featuresEnabled = this.segwitEnabled || this.taprootEnabled || this.rbfEnabled;
 | 
					    this.featuresEnabled = this.segwitEnabled || this.taprootEnabled || this.rbfEnabled;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isAuditAvailable(blockHeight: number): boolean {
 | 
				
			||||||
 | 
					    if (!this.auditEnabled) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    switch (this.stateService.network) {
 | 
				
			||||||
 | 
					      case 'testnet':
 | 
				
			||||||
 | 
					        if (blockHeight < this.stateService.env.TESTNET_BLOCK_AUDIT_START_HEIGHT) {
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 'signet':
 | 
				
			||||||
 | 
					        if (blockHeight < this.stateService.env.SIGNET_BLOCK_AUDIT_START_HEIGHT) {
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        if (blockHeight < this.stateService.env.MAINNET_BLOCK_AUDIT_START_HEIGHT) {
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  resetTransaction() {
 | 
					  resetTransaction() {
 | 
				
			||||||
    this.error = undefined;
 | 
					    this.error = undefined;
 | 
				
			||||||
    this.tx = null;
 | 
					    this.tx = null;
 | 
				
			||||||
@ -625,6 +717,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
    this.accelerationInfo = null;
 | 
					    this.accelerationInfo = null;
 | 
				
			||||||
    this.txInBlockIndex = null;
 | 
					    this.txInBlockIndex = null;
 | 
				
			||||||
    this.mempoolPosition = null;
 | 
					    this.mempoolPosition = null;
 | 
				
			||||||
 | 
					    this.pool = null;
 | 
				
			||||||
 | 
					    this.auditStatus = null;
 | 
				
			||||||
    document.body.scrollTo(0, 0);
 | 
					    document.body.scrollTo(0, 0);
 | 
				
			||||||
    this.leaveTransaction();
 | 
					    this.leaveTransaction();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -712,6 +806,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
    this.mempoolPositionSubscription.unsubscribe();
 | 
					    this.mempoolPositionSubscription.unsubscribe();
 | 
				
			||||||
    this.mempoolBlocksSubscription.unsubscribe();
 | 
					    this.mempoolBlocksSubscription.unsubscribe();
 | 
				
			||||||
    this.blocksSubscription.unsubscribe();
 | 
					    this.blocksSubscription.unsubscribe();
 | 
				
			||||||
 | 
					    this.miningSubscription?.unsubscribe();
 | 
				
			||||||
    this.leaveTransaction();
 | 
					    this.leaveTransaction();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user