2022-11-23 19:07:17 +09:00
import { Component , OnInit , OnDestroy , ViewChildren , QueryList } from '@angular/core' ;
2020-05-10 14:32:27 +07:00
import { Location } from '@angular/common' ;
import { ActivatedRoute , ParamMap , Router } from '@angular/router' ;
2020-02-16 22:15:07 +07:00
import { ElectrsApiService } from '../../services/electrs-api.service' ;
2023-06-20 14:54:25 -04:00
import { switchMap , tap , throttleTime , catchError , map , shareReplay , startWith } from 'rxjs/operators' ;
2022-02-04 12:51:45 +09:00
import { Transaction , Vout } from '../../interfaces/electrs.interface' ;
2023-06-20 14:54:25 -04:00
import { Observable , of , Subscription , asyncScheduler , EMPTY , combineLatest , forkJoin } from 'rxjs' ;
2020-02-16 22:15:07 +07:00
import { StateService } from '../../services/state.service' ;
2022-09-21 17:23:45 +02:00
import { SeoService } from '../../services/seo.service' ;
import { WebsocketService } from '../../services/websocket.service' ;
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe' ;
2022-11-23 19:07:17 +09:00
import { BlockAudit , BlockExtended , TransactionStripped } from '../../interfaces/node-api.interface' ;
2022-09-21 17:23:45 +02:00
import { ApiService } from '../../services/api.service' ;
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component' ;
import { detectWebGL } from '../../shared/graphs.utils' ;
2023-08-30 20:26:07 +09:00
import { seoDescriptionNetwork } from '../../shared/common.utils' ;
2023-02-28 10:59:39 +09:00
import { PriceService , Price } from '../../services/price.service' ;
2023-07-12 16:25:00 +09:00
import { CacheService } from '../../services/cache.service' ;
2024-01-09 12:11:45 +01:00
import { ServicesApiServices } from '../../services/services-api.service' ;
2019-11-06 15:35:02 +08:00
@Component ( {
selector : 'app-block' ,
templateUrl : './block.component.html' ,
2022-11-23 19:07:17 +09:00
styleUrls : [ './block.component.scss' ] ,
styles : [ `
. loadingGraphs {
position : absolute ;
top : 50 % ;
left : calc ( 50 % - 15 px ) ;
z - index : 100 ;
}
` ],
2019-11-06 15:35:02 +08:00
} )
2020-03-22 17:44:36 +07:00
export class BlockComponent implements OnInit , OnDestroy {
2020-05-09 20:37:50 +07:00
network = '' ;
2022-02-04 12:51:45 +09:00
block : BlockExtended ;
2022-11-23 19:07:17 +09:00
blockAudit : BlockAudit = undefined ;
2020-02-16 22:15:07 +07:00
blockHeight : number ;
2022-06-14 16:39:37 +00:00
lastBlockHeight : number ;
2021-08-12 19:49:39 -03:00
nextBlockHeight : number ;
2020-02-16 22:15:07 +07:00
blockHash : string ;
2019-11-12 16:39:59 +08:00
isLoadingBlock = true ;
2022-02-04 12:51:45 +09:00
latestBlock : BlockExtended ;
latestBlocks : BlockExtended [ ] = [ ] ;
2020-02-16 22:15:07 +07:00
transactions : Transaction [ ] ;
2019-11-12 16:39:59 +08:00
isLoadingTransactions = true ;
2022-06-14 16:39:37 +00:00
strippedTransactions : TransactionStripped [ ] ;
overviewTransitionDirection : string ;
isLoadingOverview = true ;
2019-11-13 14:51:44 +08:00
error : any ;
2020-03-04 15:10:30 +07:00
blockSubsidy : number ;
fees : number ;
2020-05-30 21:18:53 +07:00
paginationMaxSize : number ;
page = 1 ;
2020-11-23 02:30:46 +07:00
itemsPerPage : number ;
2021-01-08 21:44:36 +07:00
txsLoadingStatus$ : Observable < number > ;
2021-05-01 03:55:02 +04:00
showDetails = false ;
2021-08-12 19:49:39 -03:00
showPreviousBlocklink = true ;
showNextBlocklink = true ;
2022-05-21 02:30:38 +04:00
transactionsError : any = null ;
2022-06-14 16:39:37 +00:00
overviewError : any = null ;
webGlEnabled = true ;
2023-02-14 12:23:26 -06:00
auditSupported : boolean = this . stateService . env . AUDIT && this . stateService . env . BASE_MODULE === 'mempool' && this . stateService . env . MINING_DASHBOARD === true ;
auditModeEnabled : boolean = ! this . stateService . hideAudit . value ;
2022-12-29 07:38:57 -06:00
auditAvailable = true ;
showAudit : boolean ;
2022-11-23 19:07:17 +09:00
isMobile = window . innerWidth <= 767.98 ;
hoverTx : string ;
numMissing : number = 0 ;
numUnexpected : number = 0 ;
mode : 'projected' | 'actual' = 'projected' ;
2019-11-06 15:35:02 +08:00
2022-06-14 16:39:37 +00:00
transactionSubscription : Subscription ;
overviewSubscription : Subscription ;
2022-11-23 19:07:17 +09:00
auditSubscription : Subscription ;
2021-08-18 15:59:18 +03:00
keyNavigationSubscription : Subscription ;
blocksSubscription : Subscription ;
2023-07-12 16:25:00 +09:00
cacheBlocksSubscription : Subscription ;
2021-08-18 15:59:18 +03:00
networkChangedSubscription : Subscription ;
queryParamsSubscription : Subscription ;
2022-06-22 23:17:49 +02:00
nextBlockSubscription : Subscription = undefined ;
nextBlockSummarySubscription : Subscription = undefined ;
nextBlockTxListSubscription : Subscription = undefined ;
2022-09-29 22:37:49 +00:00
timeLtrSubscription : Subscription ;
timeLtr : boolean ;
2022-11-23 19:07:17 +09:00
childChangeSubscription : Subscription ;
2022-12-29 07:38:57 -06:00
auditPrefSubscription : Subscription ;
2023-02-21 12:36:43 +09:00
priceSubscription : Subscription ;
blockConversion : Price ;
2021-08-18 15:59:18 +03:00
2022-11-23 19:07:17 +09:00
@ViewChildren ( 'blockGraphProjected' ) blockGraphProjected : QueryList < BlockOverviewGraphComponent > ;
@ViewChildren ( 'blockGraphActual' ) blockGraphActual : QueryList < BlockOverviewGraphComponent > ;
2022-06-14 16:39:37 +00:00
2019-11-12 16:39:59 +08:00
constructor (
private route : ActivatedRoute ,
2020-05-10 14:32:27 +07:00
private location : Location ,
private router : Router ,
2020-02-16 22:15:07 +07:00
private electrsApiService : ElectrsApiService ,
2022-04-20 13:12:32 +09:00
public stateService : StateService ,
2020-03-24 00:52:08 +07:00
private seoService : SeoService ,
2020-09-26 22:46:26 +07:00
private websocketService : WebsocketService ,
2021-12-31 02:21:12 +04:00
private relativeUrlPipe : RelativeUrlPipe ,
2023-02-21 12:36:43 +09:00
private apiService : ApiService ,
private priceService : PriceService ,
2023-07-12 16:25:00 +09:00
private cacheService : CacheService ,
2024-01-09 12:11:45 +01:00
private servicesApiService : ServicesApiServices ,
2022-06-14 16:39:37 +00:00
) {
this . webGlEnabled = detectWebGL ( ) ;
}
2019-11-06 15:35:02 +08:00
ngOnInit() {
2020-09-26 22:46:26 +07:00
this . websocketService . want ( [ 'blocks' , 'mempool-blocks' ] ) ;
2021-07-05 16:28:56 -03:00
this . paginationMaxSize = window . matchMedia ( '(max-width: 670px)' ) . matches ? 3 : 5 ;
2020-06-19 23:57:57 +07:00
this . network = this . stateService . network ;
2021-01-11 21:11:09 +07:00
this . itemsPerPage = this . stateService . env . ITEMS_PER_PAGE ;
2020-05-30 21:18:53 +07:00
2022-09-29 22:37:49 +00:00
this . timeLtrSubscription = this . stateService . timeLtr . subscribe ( ( ltr ) = > {
this . timeLtr = ! ! ltr ;
} ) ;
2023-02-14 12:23:26 -06:00
this . setAuditAvailable ( this . auditSupported ) ;
2022-12-29 07:38:57 -06:00
2023-02-14 12:23:26 -06:00
if ( this . auditSupported ) {
2023-02-12 21:43:12 -06:00
this . auditPrefSubscription = this . stateService . hideAudit . subscribe ( ( hide ) = > {
this . auditModeEnabled = ! hide ;
this . showAudit = this . auditAvailable && this . auditModeEnabled ;
} ) ;
}
2022-10-28 10:31:55 -06:00
2021-01-08 21:44:36 +07:00
this . txsLoadingStatus $ = this . route . paramMap
. pipe (
switchMap ( ( ) = > this . stateService . loadingIndicators $ ) ,
map ( ( indicators ) = > indicators [ 'blocktxs-' + this . blockHash ] !== undefined ? indicators [ 'blocktxs-' + this . blockHash ] : 0 )
) ;
2023-07-12 16:25:00 +09:00
this . cacheBlocksSubscription = this . cacheService . loadedBlocks $ . subscribe ( ( block ) = > {
this . loadedCacheBlock ( block ) ;
} ) ;
2021-08-29 04:55:46 +03:00
this . blocksSubscription = this . stateService . blocks $
2023-07-08 01:07:06 -04:00
. subscribe ( ( blocks ) = > {
this . latestBlock = blocks [ 0 ] ;
this . latestBlocks = blocks ;
2021-08-29 04:55:46 +03:00
this . setNextAndPreviousBlockLink ( ) ;
2023-07-08 01:07:06 -04:00
for ( const block of blocks ) {
if ( block . id === this . blockHash ) {
this . block = block ;
2023-07-21 17:18:45 +09:00
if ( block . extras ) {
block . extras . minFee = this . getMinBlockFee ( block ) ;
block . extras . maxFee = this . getMaxBlockFee ( block ) ;
if ( block ? . extras ? . reward != undefined ) {
this . fees = block . extras . reward / 100000000 - this . blockSubsidy ;
}
2023-07-08 01:07:06 -04:00
}
2023-07-11 16:35:00 +09:00
} else if ( block . height === this . block ? . height ) {
2023-07-10 13:57:18 +09:00
this . block . stale = true ;
this . block . canonical = block . id ;
2022-02-04 12:51:45 +09:00
}
2021-08-29 04:55:46 +03:00
}
} ) ;
2022-06-14 16:39:37 +00:00
const block $ = this . route . paramMap . pipe (
2019-11-12 16:39:59 +08:00
switchMap ( ( params : ParamMap ) = > {
const blockHash : string = params . get ( 'id' ) || '' ;
2020-05-10 00:35:21 +07:00
this . block = undefined ;
2020-05-30 21:18:53 +07:00
this . page = 1 ;
2020-02-16 22:15:07 +07:00
this . error = undefined ;
2020-03-04 15:10:30 +07:00
this . fees = undefined ;
2020-02-16 22:15:07 +07:00
if ( history . state . data && history . state . data . blockHeight ) {
this . blockHeight = history . state . data . blockHeight ;
2022-12-29 07:38:57 -06:00
this . updateAuditAvailableFromBlockHeight ( this . blockHeight ) ;
2020-02-16 22:15:07 +07:00
}
2020-06-19 23:57:57 +07:00
let isBlockHeight = false ;
2020-05-10 14:32:27 +07:00
if ( /^[0-9]+$/ . test ( blockHash ) ) {
isBlockHeight = true ;
2023-09-20 03:07:35 +00:00
this . stateService . markBlock $ . next ( { blockHeight : parseInt ( blockHash , 10 ) } ) ;
2020-05-10 14:32:27 +07:00
} else {
this . blockHash = blockHash ;
}
2020-02-24 03:42:29 +07:00
document . body . scrollTo ( 0 , 0 ) ;
2020-02-16 22:15:07 +07:00
if ( history . state . data && history . state . data . block ) {
2020-02-17 20:39:20 +07:00
this . blockHeight = history . state . data . block . height ;
2022-12-29 07:38:57 -06:00
this . updateAuditAvailableFromBlockHeight ( this . blockHeight ) ;
2020-02-16 22:15:07 +07:00
return of ( history . state . data . block ) ;
} else {
this . isLoadingBlock = true ;
2022-06-22 19:08:16 +00:00
this . isLoadingOverview = true ;
2020-05-10 14:32:27 +07:00
2022-02-04 12:51:45 +09:00
let blockInCache : BlockExtended ;
2020-05-10 14:32:27 +07:00
if ( isBlockHeight ) {
2021-08-29 04:55:46 +03:00
blockInCache = this . latestBlocks . find ( ( block ) = > block . height === parseInt ( blockHash , 10 ) ) ;
if ( blockInCache ) {
return of ( blockInCache ) ;
}
2020-05-10 14:32:27 +07:00
return this . electrsApiService . getBlockHashFromHeight $ ( parseInt ( blockHash , 10 ) )
. pipe (
switchMap ( ( hash ) = > {
this . blockHash = hash ;
this . location . replaceState (
this . router . createUrlTree ( [ ( this . network ? '/' + this . network : '' ) + '/block/' , hash ] ) . toString ( )
) ;
2023-08-22 02:54:23 +09:00
this . seoService . updateCanonical ( this . location . path ( ) ) ;
2022-08-16 16:15:34 +00:00
return this . apiService . getBlock $ ( hash ) . pipe (
catchError ( ( err ) = > {
this . error = err ;
this . isLoadingBlock = false ;
this . isLoadingOverview = false ;
2023-03-09 02:34:21 -06:00
this . seoService . logSoft404 ( ) ;
2022-08-16 16:15:34 +00:00
return EMPTY ;
} )
) ;
} ) ,
catchError ( ( err ) = > {
this . error = err ;
this . isLoadingBlock = false ;
this . isLoadingOverview = false ;
2023-03-09 02:34:21 -06:00
this . seoService . logSoft404 ( ) ;
2022-08-16 16:15:34 +00:00
return EMPTY ;
} ) ,
2020-05-10 14:32:27 +07:00
) ;
}
2021-08-11 00:17:25 +05:30
2021-08-29 04:55:46 +03:00
blockInCache = this . latestBlocks . find ( ( block ) = > block . id === this . blockHash ) ;
if ( blockInCache ) {
return of ( blockInCache ) ;
}
2022-08-16 16:15:34 +00:00
return this . apiService . getBlock $ ( blockHash ) . pipe (
catchError ( ( err ) = > {
this . error = err ;
this . isLoadingBlock = false ;
this . isLoadingOverview = false ;
2023-03-09 02:34:21 -06:00
this . seoService . logSoft404 ( ) ;
2022-08-16 16:15:34 +00:00
return EMPTY ;
} )
) ;
2020-02-16 22:15:07 +07:00
}
2020-03-22 17:44:36 +07:00
} ) ,
2022-02-04 12:51:45 +09:00
tap ( ( block : BlockExtended ) = > {
2022-06-23 16:29:25 +02:00
if ( block . height > 0 ) {
// Preload previous block summary (execute the http query so the response will be cached)
this . unsubscribeNextBlockSubscriptions ( ) ;
setTimeout ( ( ) = > {
this . nextBlockSubscription = this . apiService . getBlock $ ( block . previousblockhash ) . subscribe ( ) ;
this . nextBlockTxListSubscription = this . electrsApiService . getBlockTransactions $ ( block . previousblockhash ) . subscribe ( ) ;
2023-02-14 12:23:26 -06:00
if ( this . auditSupported ) {
2023-02-12 21:43:12 -06:00
this . apiService . getBlockAudit $ ( block . previousblockhash ) ;
}
2022-06-23 16:29:25 +02:00
} , 100 ) ;
}
2022-12-29 07:38:57 -06:00
this . updateAuditAvailableFromBlockHeight ( block . height ) ;
2020-03-22 17:44:36 +07:00
this . block = block ;
2023-07-21 17:18:45 +09:00
if ( block . extras ) {
block . extras . minFee = this . getMinBlockFee ( block ) ;
block . extras . maxFee = this . getMaxBlockFee ( block ) ;
}
2020-03-22 17:44:36 +07:00
this . blockHeight = block . height ;
2022-06-14 16:39:37 +00:00
this . lastBlockHeight = this . blockHeight ;
2021-08-12 19:49:39 -03:00
this . nextBlockHeight = block . height + 1 ;
this . setNextAndPreviousBlockLink ( ) ;
2020-12-04 22:30:09 +07:00
this . seoService . setTitle ( $localize ` :@@block.component.browser-title:Block ${ block . height } :BLOCK_HEIGHT:: ${ block . id } :BLOCK_ID: ` ) ;
2023-08-30 20:26:07 +09:00
if ( this . stateService . network === 'liquid' || this . stateService . network === 'liquidtestnet' ) {
this . seoService . setDescription ( $localize ` :@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid ${ seoDescriptionNetwork ( this . stateService . network ) } block ${ block . height } :BLOCK_HEIGHT: ( ${ block . id } :BLOCK_ID:). ` ) ;
} else {
this . seoService . setDescription ( $localize ` :@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin ${ seoDescriptionNetwork ( this . stateService . network ) } block ${ block . height } :BLOCK_HEIGHT: ( ${ block . id } :BLOCK_ID:). ` ) ;
}
2020-03-22 17:44:36 +07:00
this . isLoadingBlock = false ;
this . setBlockSubsidy ( ) ;
2022-02-04 19:28:00 +09:00
if ( block ? . extras ? . reward !== undefined ) {
this . fees = block . extras . reward / 100000000 - this . blockSubsidy ;
2020-03-22 17:44:36 +07:00
}
this . stateService . markBlock $ . next ( { blockHeight : this.blockHeight } ) ;
this . isLoadingTransactions = true ;
this . transactions = null ;
2022-05-21 02:30:38 +04:00
this . transactionsError = null ;
2022-06-14 16:39:37 +00:00
this . isLoadingOverview = true ;
2022-06-22 19:08:16 +00:00
this . overviewError = null ;
2023-07-12 16:25:00 +09:00
const cachedBlock = this . cacheService . getCachedBlock ( block . height ) ;
if ( ! cachedBlock ) {
this . cacheService . loadBlock ( block . height ) ;
} else {
this . loadedCacheBlock ( cachedBlock ) ;
}
2020-03-22 17:44:36 +07:00
} ) ,
2022-06-22 19:08:16 +00:00
throttleTime ( 300 , asyncScheduler , { leading : true , trailing : true } ) ,
2022-06-14 16:39:37 +00:00
shareReplay ( 1 )
) ;
this . transactionSubscription = block $ . pipe (
2020-03-22 23:45:16 +07:00
switchMap ( ( block ) = > this . electrsApiService . getBlockTransactions $ ( block . id )
. pipe (
catchError ( ( err ) = > {
2022-05-21 02:30:38 +04:00
this . transactionsError = err ;
2020-03-22 23:45:16 +07:00
return of ( [ ] ) ;
} ) )
) ,
2019-11-12 16:39:59 +08:00
)
2020-03-22 17:44:36 +07:00
. subscribe ( ( transactions : Transaction [ ] ) = > {
2020-06-19 23:32:17 +07:00
if ( this . fees === undefined && transactions [ 0 ] ) {
2020-03-22 17:44:36 +07:00
this . fees = transactions [ 0 ] . vout . reduce ( ( acc : number , curr : Vout ) = > acc + curr . value , 0 ) / 100000000 - this . blockSubsidy ;
2020-03-04 15:10:30 +07:00
}
2020-03-22 17:44:36 +07:00
this . transactions = transactions ;
this . isLoadingTransactions = false ;
2019-11-13 14:51:44 +08:00
} ,
( error ) = > {
this . error = error ;
this . isLoadingBlock = false ;
2022-06-14 16:39:37 +00:00
this . isLoadingOverview = false ;
} ) ;
2023-06-20 14:54:25 -04:00
this . overviewSubscription = block $ . pipe (
switchMap ( ( block ) = > {
return forkJoin ( [
this . apiService . getStrippedBlockTransactions $ ( block . id )
2022-12-19 19:02:50 -06:00
. pipe (
catchError ( ( err ) = > {
this . overviewError = err ;
2023-06-20 14:54:25 -04:00
return of ( null ) ;
2022-12-19 19:02:50 -06:00
} )
2023-06-20 14:54:25 -04:00
) ,
! this . isAuditAvailableFromBlockHeight ( block . height ) ? of ( null ) : this . apiService . getBlockAudit $ ( block . id )
. pipe (
catchError ( ( err ) = > {
this . overviewError = err ;
return of ( null ) ;
} )
2023-12-29 14:13:13 +00:00
) ,
2024-01-09 12:11:45 +01:00
this . stateService . env . ACCELERATOR === true && block . height > 819500 ? this . servicesApiService . getAccelerationHistory $ ( { blockHash : block.id } ) : of ( [ ] )
2023-06-20 14:54:25 -04:00
] ) ;
} )
)
2023-12-29 14:13:13 +00:00
. subscribe ( ( [ transactions , blockAudit , accelerations ] ) = > {
2023-06-20 14:54:25 -04:00
if ( transactions ) {
this . strippedTransactions = transactions ;
} else {
this . strippedTransactions = [ ] ;
}
2023-12-29 14:13:13 +00:00
const acceleratedInBlock = { } ;
for ( const acc of accelerations ) {
acceleratedInBlock [ acc . txid ] = acc ;
}
for ( const tx of transactions ) {
if ( acceleratedInBlock [ tx . txid ] ) {
tx . acc = true ;
}
}
2023-06-20 14:54:25 -04:00
this . blockAudit = null ;
if ( transactions && blockAudit ) {
const inTemplate = { } ;
const inBlock = { } ;
const isAdded = { } ;
const isCensored = { } ;
const isMissing = { } ;
const isSelected = { } ;
const isFresh = { } ;
const isSigop = { } ;
2023-07-25 14:00:17 +09:00
const isRbf = { } ;
2023-05-22 18:16:58 -04:00
const isAccelerated = { } ;
2023-06-20 14:54:25 -04:00
this . numMissing = 0 ;
this . numUnexpected = 0 ;
if ( blockAudit ? . template ) {
for ( const tx of blockAudit . template ) {
inTemplate [ tx . txid ] = true ;
2023-07-18 16:08:25 +09:00
if ( tx . acc ) {
isAccelerated [ tx . txid ] = true ;
2022-11-23 19:07:17 +09:00
}
2023-06-20 14:54:25 -04:00
}
for ( const tx of transactions ) {
inBlock [ tx . txid ] = true ;
}
for ( const txid of blockAudit . addedTxs ) {
isAdded [ txid ] = true ;
}
for ( const txid of blockAudit . missingTxs ) {
isCensored [ txid ] = true ;
}
for ( const txid of blockAudit . freshTxs || [ ] ) {
isFresh [ txid ] = true ;
}
for ( const txid of blockAudit . sigopTxs || [ ] ) {
isSigop [ txid ] = true ;
}
2023-06-19 18:14:09 -04:00
for ( const txid of blockAudit . fullrbfTxs || [ ] ) {
2023-07-25 14:00:17 +09:00
isRbf [ txid ] = true ;
2023-06-19 18:14:09 -04:00
}
2023-05-22 18:16:58 -04:00
for ( const txid of blockAudit . acceleratedTxs || [ ] ) {
isAccelerated [ txid ] = true ;
}
2023-06-20 14:54:25 -04:00
// set transaction statuses
for ( const tx of blockAudit . template ) {
tx . context = 'projected' ;
if ( isCensored [ tx . txid ] ) {
tx . status = 'censored' ;
} else if ( inBlock [ tx . txid ] ) {
tx . status = 'found' ;
} else {
2023-06-19 18:14:09 -04:00
if ( isFresh [ tx . txid ] ) {
2023-07-16 13:49:33 +09:00
if ( tx . rate - ( tx . fee / tx . vsize ) >= 0.1 ) {
tx . status = 'freshcpfp' ;
} else {
tx . status = 'fresh' ;
}
2023-06-19 18:14:09 -04:00
} else if ( isSigop [ tx . txid ] ) {
tx . status = 'sigop' ;
2023-07-25 14:00:17 +09:00
} else if ( isRbf [ tx . txid ] ) {
tx . status = 'rbf' ;
2022-11-23 19:07:17 +09:00
} else {
2023-06-19 18:14:09 -04:00
tx . status = 'missing' ;
2022-11-23 19:07:17 +09:00
}
2023-06-20 14:54:25 -04:00
isMissing [ tx . txid ] = true ;
this . numMissing ++ ;
2022-11-23 19:07:17 +09:00
}
2023-05-22 18:16:58 -04:00
if ( isAccelerated [ tx . txid ] ) {
tx . status = 'accelerated' ;
}
2023-06-20 14:54:25 -04:00
}
for ( const [ index , tx ] of transactions . entries ( ) ) {
tx . context = 'actual' ;
if ( index === 0 ) {
tx . status = null ;
} else if ( isAdded [ tx . txid ] ) {
tx . status = 'added' ;
} else if ( inTemplate [ tx . txid ] ) {
tx . status = 'found' ;
2023-07-25 14:00:17 +09:00
} else if ( isRbf [ tx . txid ] ) {
tx . status = 'rbf' ;
2023-06-20 14:54:25 -04:00
} else {
tx . status = 'selected' ;
isSelected [ tx . txid ] = true ;
this . numUnexpected ++ ;
2022-11-23 19:07:17 +09:00
}
2023-05-22 18:16:58 -04:00
if ( isAccelerated [ tx . txid ] ) {
tx . status = 'accelerated' ;
2022-11-23 19:07:17 +09:00
}
}
2023-06-20 14:54:25 -04:00
for ( const tx of transactions ) {
inBlock [ tx . txid ] = true ;
}
blockAudit . feeDelta = blockAudit . expectedFees > 0 ? ( blockAudit . expectedFees - this . block . extras . totalFees ) / blockAudit.expectedFees : 0 ;
blockAudit . weightDelta = blockAudit . expectedWeight > 0 ? ( blockAudit . expectedWeight - this . block . weight ) / blockAudit.expectedWeight : 0 ;
blockAudit . txDelta = blockAudit . template . length > 0 ? ( blockAudit . template . length - this . block . tx_count ) / blockAudit.template.length : 0 ;
this . blockAudit = blockAudit ;
this . setAuditAvailable ( true ) ;
} else {
2022-12-29 07:38:57 -06:00
this . setAuditAvailable ( false ) ;
2023-06-20 14:54:25 -04:00
}
} else {
this . setAuditAvailable ( false ) ;
}
this . isLoadingOverview = false ;
this . setupBlockGraphs ( ) ;
} ) ;
2019-11-12 16:39:59 +08:00
2021-08-18 15:59:18 +03:00
this . networkChangedSubscription = this . stateService . networkChanged $
2020-05-09 20:37:50 +07:00
. subscribe ( ( network ) = > this . network = network ) ;
2021-05-01 03:55:02 +04:00
2021-08-18 15:59:18 +03:00
this . queryParamsSubscription = this . route . queryParams . subscribe ( ( params ) = > {
2021-05-01 03:55:02 +04:00
if ( params . showDetails === 'true' ) {
this . showDetails = true ;
} else {
this . showDetails = false ;
}
2022-11-23 19:07:17 +09:00
if ( params . view === 'projected' ) {
this . mode = 'projected' ;
} else {
this . mode = 'actual' ;
}
this . setupBlockGraphs ( ) ;
2021-05-01 03:55:02 +04:00
} ) ;
2021-08-12 19:49:39 -03:00
2021-08-18 15:59:18 +03:00
this . keyNavigationSubscription = this . stateService . keyNavigation $ . subscribe ( ( event ) = > {
2022-09-29 22:37:49 +00:00
const prevKey = this . timeLtr ? 'ArrowLeft' : 'ArrowRight' ;
const nextKey = this . timeLtr ? 'ArrowRight' : 'ArrowLeft' ;
if ( this . showPreviousBlocklink && event . key === prevKey && this . nextBlockHeight - 2 >= 0 ) {
2021-08-18 12:49:42 +03:00
this . navigateToPreviousBlock ( ) ;
2021-08-12 19:49:39 -03:00
}
2022-09-29 22:37:49 +00:00
if ( event . key === nextKey ) {
2021-08-25 15:44:55 +03:00
if ( this . showNextBlocklink ) {
this . navigateToNextBlock ( ) ;
} else {
2021-12-31 02:21:12 +04:00
this . router . navigate ( [ this . relativeUrlPipe . transform ( '/mempool-block' ) , '0' ] ) ;
2021-08-25 15:44:55 +03:00
}
2021-08-12 19:49:39 -03:00
}
} ) ;
2023-02-21 12:36:43 +09:00
if ( this . priceSubscription ) {
this . priceSubscription . unsubscribe ( ) ;
}
this . priceSubscription = block $ . pipe (
switchMap ( ( block ) = > {
2023-02-23 09:50:34 +09:00
return this . priceService . getBlockPrice $ ( block . timestamp ) . pipe (
tap ( ( price ) = > {
this . blockConversion = price ;
2023-02-21 12:36:43 +09:00
} )
) ;
} )
) . subscribe ( ) ;
2020-02-26 04:29:57 +07:00
}
2022-11-23 19:07:17 +09:00
ngAfterViewInit ( ) : void {
this . childChangeSubscription = combineLatest ( [ this . blockGraphProjected . changes . pipe ( startWith ( null ) ) , this . blockGraphActual . changes . pipe ( startWith ( null ) ) ] ) . subscribe ( ( ) = > {
this . setupBlockGraphs ( ) ;
} ) ;
}
2020-03-22 17:44:36 +07:00
ngOnDestroy() {
this . stateService . markBlock $ . next ( { } ) ;
2023-01-27 20:01:31 -06:00
this . transactionSubscription ? . unsubscribe ( ) ;
2022-11-23 19:07:17 +09:00
this . overviewSubscription ? . unsubscribe ( ) ;
this . auditSubscription ? . unsubscribe ( ) ;
2023-01-27 20:01:31 -06:00
this . keyNavigationSubscription ? . unsubscribe ( ) ;
this . blocksSubscription ? . unsubscribe ( ) ;
2023-07-12 16:25:00 +09:00
this . cacheBlocksSubscription ? . unsubscribe ( ) ;
2023-01-27 20:01:31 -06:00
this . networkChangedSubscription ? . unsubscribe ( ) ;
this . queryParamsSubscription ? . unsubscribe ( ) ;
this . timeLtrSubscription ? . unsubscribe ( ) ;
this . auditSubscription ? . unsubscribe ( ) ;
2022-06-22 23:17:49 +02:00
this . unsubscribeNextBlockSubscriptions ( ) ;
2023-01-27 20:01:31 -06:00
this . childChangeSubscription ? . unsubscribe ( ) ;
2023-02-23 18:43:32 +09:00
this . priceSubscription ? . unsubscribe ( ) ;
2022-06-22 23:17:49 +02:00
}
unsubscribeNextBlockSubscriptions() {
if ( this . nextBlockSubscription !== undefined ) {
this . nextBlockSubscription . unsubscribe ( ) ;
}
if ( this . nextBlockSummarySubscription !== undefined ) {
this . nextBlockSummarySubscription . unsubscribe ( ) ;
}
if ( this . nextBlockTxListSubscription !== undefined ) {
this . nextBlockTxListSubscription . unsubscribe ( ) ;
}
2020-03-22 17:44:36 +07:00
}
2022-04-20 13:30:06 +09:00
// TODO - Refactor this.fees/this.reward for liquid because it is not
// used anymore on Bitcoin networks (we use block.extras directly)
2020-02-26 04:29:57 +07:00
setBlockSubsidy() {
2022-04-20 13:30:06 +09:00
this . blockSubsidy = 0 ;
2019-11-12 16:39:59 +08:00
}
2021-07-16 09:33:22 -03:00
pageChange ( page : number , target : HTMLElement ) {
2020-05-30 21:18:53 +07:00
const start = ( page - 1 ) * this . itemsPerPage ;
2019-11-12 16:39:59 +08:00
this . isLoadingTransactions = true ;
2020-05-30 21:18:53 +07:00
this . transactions = null ;
2022-05-21 02:30:38 +04:00
this . transactionsError = null ;
2021-07-16 09:33:22 -03:00
target . scrollIntoView ( ) ; // works for chrome
2020-05-30 21:18:53 +07:00
this . electrsApiService . getBlockTransactions $ ( this . block . id , start )
2022-05-21 02:30:38 +04:00
. pipe (
catchError ( ( err ) = > {
this . transactionsError = err ;
return of ( [ ] ) ;
} )
)
2020-05-30 21:18:53 +07:00
. subscribe ( ( transactions ) = > {
this . transactions = transactions ;
2019-11-12 16:39:59 +08:00
this . isLoadingTransactions = false ;
2021-07-16 09:33:22 -03:00
target . scrollIntoView ( ) ; // works for firefox
2019-11-12 16:39:59 +08:00
} ) ;
2019-11-06 15:35:02 +08:00
}
2021-05-01 03:55:02 +04:00
toggleShowDetails() {
if ( this . showDetails ) {
this . showDetails = false ;
this . router . navigate ( [ ] , {
relativeTo : this.route ,
2022-11-23 19:07:17 +09:00
queryParams : { showDetails : false , view : this.mode } ,
2021-05-01 03:55:02 +04:00
queryParamsHandling : 'merge' ,
2021-05-10 22:18:30 -07:00
fragment : 'block'
2021-05-01 03:55:02 +04:00
} ) ;
} else {
this . showDetails = true ;
this . router . navigate ( [ ] , {
relativeTo : this.route ,
2022-11-23 19:07:17 +09:00
queryParams : { showDetails : true , view : this.mode } ,
2021-05-01 03:55:02 +04:00
queryParamsHandling : 'merge' ,
2021-05-10 22:18:30 -07:00
fragment : 'details'
2021-05-01 03:55:02 +04:00
} ) ;
}
}
hasTaproot ( version : number ) : boolean {
2021-05-01 21:03:01 +04:00
const versionBit = 2 ; // Taproot
return ( Number ( version ) & ( 1 << versionBit ) ) === ( 1 << versionBit ) ;
}
displayTaprootStatus ( ) : boolean {
if ( this . stateService . network !== '' ) {
return false ;
}
2021-05-01 21:46:49 +04:00
return this . block && this . block . height > 681393 && ( new Date ( ) . getTime ( ) / 1000 ) < 1628640000 ;
2021-05-01 03:55:02 +04:00
}
2021-07-05 16:28:56 -03:00
2021-08-18 12:49:42 +03:00
navigateToPreviousBlock() {
2021-08-25 15:44:55 +03:00
if ( ! this . block ) {
return ;
}
2021-08-18 12:49:42 +03:00
const block = this . latestBlocks . find ( ( b ) = > b . height === this . nextBlockHeight - 2 ) ;
2021-12-31 02:21:12 +04:00
this . router . navigate ( [ this . relativeUrlPipe . transform ( '/block/' ) ,
2021-08-29 04:55:46 +03:00
block ? block.id : this.block.previousblockhash ] , { state : { data : { block , blockHeight : this.nextBlockHeight - 2 } } } ) ;
2021-08-18 12:49:42 +03:00
}
navigateToNextBlock() {
const block = this . latestBlocks . find ( ( b ) = > b . height === this . nextBlockHeight ) ;
2021-12-31 02:21:12 +04:00
this . router . navigate ( [ this . relativeUrlPipe . transform ( '/block/' ) ,
2021-08-29 04:55:46 +03:00
block ? block.id : this.nextBlockHeight ] , { state : { data : { block , blockHeight : this.nextBlockHeight } } } ) ;
2021-08-18 12:49:42 +03:00
}
2021-08-12 19:49:39 -03:00
setNextAndPreviousBlockLink ( ) {
2022-09-29 22:37:49 +00:00
if ( this . latestBlock ) {
if ( ! this . blockHeight ) {
2021-08-12 19:49:39 -03:00
this . showPreviousBlocklink = false ;
} else {
this . showPreviousBlocklink = true ;
}
if ( this . latestBlock . height && this . latestBlock . height === this . blockHeight ) {
this . showNextBlocklink = false ;
2021-08-18 12:49:42 +03:00
} else {
2021-08-12 19:49:39 -03:00
this . showNextBlocklink = true ;
}
}
}
2022-06-15 01:40:05 +00:00
2022-11-23 19:07:17 +09:00
setupBlockGraphs ( ) : void {
if ( this . blockAudit || this . strippedTransactions ) {
this . blockGraphProjected . forEach ( graph = > {
graph . destroy ( ) ;
if ( this . isMobile && this . mode === 'actual' ) {
graph . setup ( this . blockAudit ? . transactions || this . strippedTransactions || [ ] ) ;
} else {
graph . setup ( this . blockAudit ? . template || [ ] ) ;
}
} ) ;
this . blockGraphActual . forEach ( graph = > {
graph . destroy ( ) ;
graph . setup ( this . blockAudit ? . transactions || this . strippedTransactions || [ ] ) ;
} ) ;
}
}
onResize ( event : any ) : void {
const isMobile = event . target . innerWidth <= 767.98 ;
const changed = isMobile !== this . isMobile ;
this . isMobile = isMobile ;
this . paginationMaxSize = event . target . innerWidth < 670 ? 3 : 5 ;
if ( changed ) {
this . changeMode ( this . mode ) ;
}
}
changeMode ( mode : 'projected' | 'actual' ) : void {
this . router . navigate ( [ ] , {
relativeTo : this.route ,
queryParams : { showDetails : this.showDetails , view : mode } ,
queryParamsHandling : 'merge' ,
fragment : 'overview'
} ) ;
}
2023-04-02 07:40:05 +09:00
onTxClick ( event : { tx : TransactionStripped , keyModifier : boolean } ) : void {
const url = new RelativeUrlPipe ( this . stateService ) . transform ( ` /tx/ ${ event . tx . txid } ` ) ;
if ( ! event . keyModifier ) {
this . router . navigate ( [ url ] ) ;
} else {
window . open ( url , '_blank' ) ;
}
2022-06-15 01:40:05 +00:00
}
2022-11-23 19:07:17 +09:00
onTxHover ( txid : string ) : void {
if ( txid && txid . length ) {
this . hoverTx = txid ;
} else {
this . hoverTx = null ;
}
}
2022-12-01 19:48:57 +09:00
2022-12-29 07:38:57 -06:00
setAuditAvailable ( available : boolean ) : void {
this . auditAvailable = available ;
2023-02-14 12:23:26 -06:00
this . showAudit = this . auditAvailable && this . auditModeEnabled && this . auditSupported ;
2022-12-29 07:38:57 -06:00
}
2023-01-26 11:09:23 -06:00
toggleAuditMode ( ) : void {
this . stateService . hideAudit . next ( this . auditModeEnabled ) ;
2022-12-29 07:38:57 -06:00
}
updateAuditAvailableFromBlockHeight ( blockHeight : number ) : void {
2023-06-20 14:54:25 -04:00
if ( ! this . isAuditAvailableFromBlockHeight ( blockHeight ) ) {
2023-02-12 21:43:12 -06:00
this . setAuditAvailable ( false ) ;
}
2023-06-20 14:54:25 -04:00
}
2023-08-30 20:26:07 +09:00
2023-06-20 14:54:25 -04:00
isAuditAvailableFromBlockHeight ( blockHeight : number ) : boolean {
if ( ! this . auditSupported ) {
return false ;
}
2022-12-01 19:48:57 +09:00
switch ( this . stateService . network ) {
case 'testnet' :
if ( blockHeight < this . stateService . env . TESTNET_BLOCK_AUDIT_START_HEIGHT ) {
2023-06-20 14:54:25 -04:00
return false ;
2022-12-01 19:48:57 +09:00
}
break ;
case 'signet' :
if ( blockHeight < this . stateService . env . SIGNET_BLOCK_AUDIT_START_HEIGHT ) {
2023-06-20 14:54:25 -04:00
return false ;
2022-12-01 19:48:57 +09:00
}
break ;
default :
if ( blockHeight < this . stateService . env . MAINNET_BLOCK_AUDIT_START_HEIGHT ) {
2023-06-20 14:54:25 -04:00
return false ;
2022-12-01 19:48:57 +09:00
}
}
2023-06-20 14:54:25 -04:00
return true ;
2022-12-01 19:48:57 +09:00
}
2023-06-14 19:04:09 -04:00
getMinBlockFee ( block : BlockExtended ) : number {
if ( block ? . extras ? . feeRange ) {
// heuristic to check if feeRange is adjusted for effective rates
if ( block . extras . medianFee === block . extras . feeRange [ 3 ] ) {
return block . extras . feeRange [ 1 ] ;
} else {
return block . extras . feeRange [ 0 ] ;
}
}
return 0 ;
}
getMaxBlockFee ( block : BlockExtended ) : number {
if ( block ? . extras ? . feeRange ) {
return block . extras . feeRange [ block . extras . feeRange . length - 1 ] ;
}
return 0 ;
}
2023-07-12 16:25:00 +09:00
loadedCacheBlock ( block : BlockExtended ) : void {
2023-07-13 11:58:29 +09:00
if ( this . block && block . height === this . block . height && block . id !== this . block . id ) {
2023-07-12 16:25:00 +09:00
this . block . stale = true ;
this . block . canonical = block . id ;
}
2022-12-01 19:48:57 +09:00
}
2023-08-30 20:26:07 +09:00
}