2024-05-19 00:39:33 +07:00
import { Component , OnInit , OnDestroy , ViewChildren , QueryList , ChangeDetectorRef } from '@angular/core' ;
2020-05-10 14:32:27 +07:00
import { Location } from '@angular/common' ;
2025-01-06 18:41:46 +00:00
import { ActivatedRoute , ParamMap , Params , Router } from '@angular/router' ;
2024-10-22 21:05:01 +09:00
import { ElectrsApiService } from '@app/services/electrs-api.service' ;
2025-01-06 18:41:46 +00:00
import { switchMap , tap , throttleTime , catchError , map , shareReplay , startWith , filter , take } from 'rxjs/operators' ;
2023-06-20 14:54:25 -04:00
import { Observable , of , Subscription , asyncScheduler , EMPTY , combineLatest , forkJoin } from 'rxjs' ;
2024-10-22 21:05:01 +09:00
import { StateService } from '@app/services/state.service' ;
import { SeoService } from '@app/services/seo.service' ;
import { WebsocketService } from '@app/services/websocket.service' ;
import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pipe' ;
2024-10-23 11:09:38 +09:00
import { Acceleration , BlockAudit , BlockExtended , TransactionStripped } from '@interfaces/node-api.interface' ;
2024-10-22 21:05:01 +09:00
import { ApiService } from '@app/services/api.service' ;
import { BlockOverviewGraphComponent } from '@components/block-overview-graph/block-overview-graph.component' ;
import { detectWebGL } from '@app/shared/graphs.utils' ;
import { seoDescriptionNetwork } from '@app/shared/common.utils' ;
import { PriceService , Price } from '@app/services/price.service' ;
import { CacheService } from '@app/services/cache.service' ;
import { ServicesApiServices } from '@app/services/services-api.service' ;
import { PreloadService } from '@app/services/preload.service' ;
import { identifyPrioritizedTransactions } from '@app/shared/transaction.utils' ;
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 [ ] = [ ] ;
2024-03-05 00:40:49 +00:00
oobFees : number = 0 ;
2022-06-14 16:39:37 +00:00
strippedTransactions : TransactionStripped [ ] ;
2024-06-16 14:36:24 +00:00
accelerations : Acceleration [ ] ;
2022-06-14 16:39:37 +00:00
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 ;
2024-05-19 00:39:33 +07:00
block$ : Observable < any > ;
2021-05-01 03:55:02 +04:00
showDetails = false ;
2021-08-12 19:49:39 -03:00
showPreviousBlocklink = true ;
showNextBlocklink = true ;
2022-06-14 16:39:37 +00:00
overviewError : any = null ;
webGlEnabled = true ;
2024-03-08 18:24:18 -08:00
auditParamEnabled : boolean = false ;
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 ;
2024-05-19 00:39:33 +07:00
paginationMaxSize = window . matchMedia ( '(max-width: 670px)' ) . matches ? 3 : 5 ;
2022-11-23 19:07:17 +09:00
numUnexpected : number = 0 ;
mode : 'projected' | 'actual' = 'projected' ;
2025-01-06 18:41:46 +00:00
currentQueryParams : Params ;
2019-11-06 15:35:02 +08:00
2022-06-14 16:39:37 +00:00
overviewSubscription : Subscription ;
2024-06-16 14:36:24 +00:00
accelerationsSubscription : 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-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 ;
2025-01-06 18:41:46 +00:00
isAuditEnabledSubscription : Subscription ;
2024-03-05 00:40:49 +00:00
oobSubscription : 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 ,
2023-10-29 18:21:13 +00:00
private cd : ChangeDetectorRef ,
2024-05-22 15:28:27 +07:00
private preloadService : PreloadService ,
2022-06-14 16:39:37 +00:00
) {
2024-03-20 09:57:05 +00:00
this . webGlEnabled = this . stateService . isBrowser && detectWebGL ( ) ;
2022-06-14 16:39:37 +00:00
}
2019-11-06 15:35:02 +08:00
2024-05-19 00:39:33 +07:00
ngOnInit ( ) : void {
2020-09-26 22:46:26 +07:00
this . websocketService . want ( [ 'blocks' , 'mempool-blocks' ] ) ;
2020-06-19 23:57:57 +07:00
this . network = this . stateService . network ;
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 ) {
2025-01-06 18:41:46 +00:00
this . isAuditEnabledSubscription = this . isAuditEnabledFromParam ( ) . subscribe ( auditParam = > {
2024-03-08 18:24:18 -08:00
if ( this . auditParamEnabled ) {
this . auditModeEnabled = auditParam ;
} else {
this . auditPrefSubscription = this . stateService . hideAudit . subscribe ( hide = > {
this . auditModeEnabled = ! hide ;
this . showAudit = this . auditAvailable && this . auditModeEnabled ;
} ) ;
}
2023-02-12 21:43:12 -06:00
} ) ;
}
2022-10-28 10:31:55 -06:00
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
}
} ) ;
2024-05-19 00:39:33 +07:00
this . 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-02-16 22:15:07 +07:00
this . error = undefined ;
2020-03-04 15:10:30 +07:00
this . fees = undefined ;
2024-03-05 00:40:49 +00:00
this . oobFees = 0 ;
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 ;
2024-06-16 14:36:24 +00:00
this . strippedTransactions = undefined ;
this . blockAudit = undefined ;
this . accelerations = undefined ;
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 ) = > {
2024-05-22 15:28:27 +07:00
if ( block . previousblockhash ) {
this . preloadService . block $ . next ( block . previousblockhash ) ;
if ( this . auditSupported ) {
this . preloadService . blockAudit $ . next ( block . previousblockhash ) ;
}
2022-06-23 16:29:25 +02:00
}
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 } ) ;
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 } ) ,
2025-01-06 18:41:46 +00:00
shareReplay ( { bufferSize : 1 , refCount : true } )
2022-06-14 16:39:37 +00:00
) ;
2024-05-19 00:39:33 +07:00
this . overviewSubscription = this . block $ . pipe (
2023-06-20 14:54:25 -04:00
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 ) ;
} )
2024-06-16 14:36:24 +00:00
)
2023-06-20 14:54:25 -04:00
] ) ;
} )
)
2024-06-16 14:36:24 +00:00
. subscribe ( ( [ transactions , blockAudit ] ) = > {
2023-06-20 14:54:25 -04:00
if ( transactions ) {
this . strippedTransactions = transactions ;
} else {
this . strippedTransactions = [ ] ;
}
2024-06-16 14:36:24 +00:00
this . blockAudit = blockAudit ;
2023-06-20 14:54:25 -04:00
2024-06-16 14:36:24 +00:00
this . setupBlockAudit ( ) ;
this . isLoadingOverview = false ;
} ) ;
2023-06-20 14:54:25 -04:00
2024-06-16 14:36:24 +00:00
this . accelerationsSubscription = this . block $ . pipe (
switchMap ( ( block ) = > {
return this . stateService . env . ACCELERATOR === true && block . height > 819500
2024-08-28 14:38:12 +00:00
? this . servicesApiService . getAllAccelerationHistory $ ( { blockHeight : block.height } )
2024-06-16 14:36:24 +00:00
. pipe ( catchError ( ( ) = > {
return of ( [ ] ) ;
} ) )
: of ( [ ] ) ;
} )
) . subscribe ( ( accelerations ) = > {
this . accelerations = accelerations ;
2024-09-20 15:40:48 +02:00
if ( accelerations . length && this . strippedTransactions ) { // Don't call setupBlockAudit if we don't have transactions yet; it will be called later in overviewSubscription
2024-06-16 14:36:24 +00:00
this . setupBlockAudit ( ) ;
2023-06-20 14:54:25 -04:00
}
} ) ;
2019-11-12 16:39:59 +08:00
2024-05-19 00:39:33 +07:00
this . oobSubscription = this . block $ . pipe (
2024-04-02 08:23:38 +00:00
filter ( ( ) = > this . stateService . env . PUBLIC_ACCELERATIONS === true && this . stateService . network === '' ) ,
2024-03-05 00:40:49 +00:00
switchMap ( ( block ) = > this . apiService . getAccelerationsByHeight $ ( block . height )
. pipe (
map ( accelerations = > {
return { block , accelerations } ;
} ) ,
2024-05-19 00:39:33 +07:00
catchError ( ( ) = > {
2024-03-05 00:40:49 +00:00
return of ( { block , accelerations : [ ] } ) ;
} ) )
) ,
) . subscribe ( ( { block , accelerations } ) = > {
let totalFees = 0 ;
for ( const acc of accelerations ) {
totalFees += acc . boost_cost ;
}
this . oobFees = totalFees ;
2024-03-07 19:52:42 +00:00
if ( block && this . block && this . blockAudit && block ? . height === this . block ? . height ) {
2024-03-05 00:40:49 +00:00
this . blockAudit . feeDelta = this . blockAudit . expectedFees > 0 ? ( this . blockAudit . expectedFees - ( this . block . extras . totalFees + this . oobFees ) ) / this . blockAudit.expectedFees : 0 ;
}
} ,
( error ) = > {
this . error = error ;
this . isLoadingBlock = false ;
this . isLoadingOverview = false ;
} ) ;
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 ) = > {
2025-01-06 18:41:46 +00:00
this . currentQueryParams = 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 ( ) ;
}
2024-05-19 00:39:33 +07:00
this . priceSubscription = combineLatest ( [ this . stateService . fiatCurrency $ , this . block $ ] ) . pipe (
2024-03-09 16:32:21 +01:00
switchMap ( ( [ currency , block ] ) = > {
return this . priceService . getBlockPrice $ ( block . timestamp , true , currency ) . pipe (
2023-02-23 09:50:34 +09:00
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 ( ) ;
} ) ;
}
2024-05-19 00:39:33 +07:00
ngOnDestroy ( ) : void {
2020-03-22 17:44:36 +07:00
this . stateService . markBlock $ . next ( { } ) ;
2022-11-23 19:07:17 +09:00
this . overviewSubscription ? . unsubscribe ( ) ;
2025-01-06 18:41:46 +00:00
this . accelerationsSubscription ? . 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 . childChangeSubscription ? . unsubscribe ( ) ;
2025-01-06 18:41:46 +00:00
this . auditPrefSubscription ? . unsubscribe ( ) ;
this . isAuditEnabledSubscription ? . unsubscribe ( ) ;
2024-03-05 00:40:49 +00:00
this . oobSubscription ? . unsubscribe ( ) ;
2025-01-06 18:41:46 +00:00
this . priceSubscription ? . unsubscribe ( ) ;
2025-01-06 19:02:24 +00:00
this . blockGraphProjected . forEach ( graph = > {
graph . destroy ( ) ;
} ) ;
this . blockGraphActual . forEach ( graph = > {
graph . destroy ( ) ;
} ) ;
2022-06-22 23:17:49 +02: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)
2024-05-19 00:39:33 +07:00
setBlockSubsidy ( ) : void {
2022-04-20 13:30:06 +09:00
this . blockSubsidy = 0 ;
2019-11-12 16:39:59 +08:00
}
2024-05-19 00:39:33 +07:00
toggleShowDetails ( ) : void {
2021-05-01 03:55:02 +04:00
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
2024-05-19 00:39:33 +07:00
navigateToPreviousBlock ( ) : void {
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
}
2024-05-19 00:39:33 +07:00
navigateToNextBlock ( ) : void {
2021-08-18 12:49:42 +03:00
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
}
2024-05-19 00:39:33 +07:00
setNextAndPreviousBlockLink ( ) : void {
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
2024-06-16 14:36:24 +00:00
setupBlockAudit ( ) : void {
const transactions = this . strippedTransactions || [ ] ;
const blockAudit = this . blockAudit ;
const accelerations = this . accelerations || [ ] ;
const acceleratedInBlock = { } ;
for ( const acc of accelerations ) {
if ( acc . pools ? . some ( pool = > pool === this . block ? . extras ? . pool . id ) ) {
acceleratedInBlock [ acc . txid ] = acc ;
}
}
for ( const tx of transactions ) {
if ( acceleratedInBlock [ tx . txid ] ) {
tx . acc = true ;
const acceleration = acceleratedInBlock [ tx . txid ] ;
const boostCost = acceleration . boostCost || acceleration . bidBoost ;
const acceleratedFeeRate = Math . max ( acceleration . effectiveFee , acceleration . effectiveFee + boostCost ) / acceleration . effectiveVsize ;
if ( acceleratedFeeRate > tx . rate ) {
tx . rate = acceleratedFeeRate ;
}
} else {
tx . acc = false ;
}
}
if ( transactions && blockAudit ) {
const inTemplate = { } ;
const inBlock = { } ;
2024-07-20 15:24:56 +00:00
const isUnseen = { } ;
2024-06-16 14:36:24 +00:00
const isAdded = { } ;
const isPrioritized = { } ;
2024-08-17 00:14:33 +00:00
const isDeprioritized = { } ;
2024-06-16 14:36:24 +00:00
const isCensored = { } ;
const isMissing = { } ;
const isSelected = { } ;
const isFresh = { } ;
const isSigop = { } ;
const isRbf = { } ;
const isAccelerated = { } ;
this . numMissing = 0 ;
this . numUnexpected = 0 ;
if ( blockAudit ? . template ) {
2024-08-17 00:14:33 +00:00
// augment with locally calculated *de*prioritized transactions if possible
const { prioritized , deprioritized } = identifyPrioritizedTransactions ( transactions ) ;
// but if the local calculation produces returns unexpected results, don't use it
let useLocalDeprioritized = deprioritized . length < ( transactions . length * 0.1 ) ;
for ( const tx of prioritized ) {
if ( ! isPrioritized [ tx ] && ! isAccelerated [ tx ] ) {
useLocalDeprioritized = false ;
break ;
}
}
2024-06-16 14:36:24 +00:00
for ( const tx of blockAudit . template ) {
inTemplate [ tx . txid ] = true ;
if ( tx . acc ) {
isAccelerated [ tx . txid ] = true ;
}
}
for ( const tx of transactions ) {
inBlock [ tx . txid ] = true ;
}
2024-07-20 15:24:56 +00:00
for ( const txid of blockAudit . unseenTxs || [ ] ) {
isUnseen [ txid ] = true ;
}
2024-06-16 14:36:24 +00:00
for ( const txid of blockAudit . addedTxs ) {
isAdded [ txid ] = true ;
}
2024-08-17 00:14:33 +00:00
for ( const txid of blockAudit . prioritizedTxs ) {
2024-06-16 14:36:24 +00:00
isPrioritized [ txid ] = true ;
}
2024-08-17 00:14:33 +00:00
if ( useLocalDeprioritized ) {
for ( const txid of deprioritized || [ ] ) {
isDeprioritized [ txid ] = true ;
}
}
2024-06-16 14:36:24 +00:00
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 ;
}
for ( const txid of blockAudit . fullrbfTxs || [ ] ) {
isRbf [ txid ] = true ;
}
for ( const txid of blockAudit . acceleratedTxs || [ ] ) {
isAccelerated [ txid ] = true ;
}
// 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 {
if ( isFresh [ tx . txid ] ) {
if ( tx . rate - ( tx . fee / tx . vsize ) >= 0.1 ) {
tx . status = 'freshcpfp' ;
} else {
tx . status = 'fresh' ;
}
} else if ( isSigop [ tx . txid ] ) {
tx . status = 'sigop' ;
} else if ( isRbf [ tx . txid ] ) {
tx . status = 'rbf' ;
} else {
tx . status = 'missing' ;
}
isMissing [ tx . txid ] = true ;
this . numMissing ++ ;
}
if ( isAccelerated [ tx . txid ] ) {
tx . status = 'accelerated' ;
}
}
2024-07-20 15:24:56 +00:00
let anySeen = false ;
for ( let index = transactions . length - 1 ; index >= 0 ; index -- ) {
const tx = transactions [ index ] ;
2024-06-16 14:36:24 +00:00
tx . context = 'actual' ;
if ( index === 0 ) {
tx . status = null ;
} else if ( isPrioritized [ tx . txid ] ) {
2024-08-02 10:46:07 +00:00
if ( isAdded [ tx . txid ] || ( blockAudit . version > 0 && isUnseen [ tx . txid ] ) ) {
tx . status = 'added_prioritized' ;
} else {
tx . status = 'prioritized' ;
}
2024-08-17 00:14:33 +00:00
} else if ( isDeprioritized [ tx . txid ] ) {
if ( isAdded [ tx . txid ] || ( blockAudit . version > 0 && isUnseen [ tx . txid ] ) ) {
tx . status = 'added_deprioritized' ;
} else {
tx . status = 'deprioritized' ;
}
2024-07-20 15:24:56 +00:00
} else if ( isAdded [ tx . txid ] && ( blockAudit . version === 0 || isUnseen [ tx . txid ] ) ) {
tx . status = 'added' ;
2024-06-16 14:36:24 +00:00
} else if ( inTemplate [ tx . txid ] ) {
2024-07-20 15:24:56 +00:00
anySeen = true ;
2024-06-16 14:36:24 +00:00
tx . status = 'found' ;
} else if ( isRbf [ tx . txid ] ) {
tx . status = 'rbf' ;
2024-07-20 15:24:56 +00:00
} else if ( isUnseen [ tx . txid ] && anySeen ) {
tx . status = 'added' ;
2024-06-16 14:36:24 +00:00
} else {
tx . status = 'selected' ;
isSelected [ tx . txid ] = true ;
this . numUnexpected ++ ;
}
if ( isAccelerated [ tx . txid ] ) {
tx . status = 'accelerated' ;
}
}
for ( const tx of transactions ) {
inBlock [ tx . txid ] = true ;
}
blockAudit . feeDelta = blockAudit . expectedFees > 0 ? ( blockAudit . expectedFees - ( this . block ? . extras . totalFees + this . oobFees ) ) / 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 {
this . setAuditAvailable ( false ) ;
}
} else {
this . setAuditAvailable ( false ) ;
}
this . setupBlockGraphs ( ) ;
this . cd . markForCheck ( ) ;
}
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 || [ ] ) ;
} ) ;
}
}
2024-05-19 00:39:33 +07:00
onResize ( event : Event ) : void {
const target = event . target as Window ;
const isMobile = target . innerWidth <= 767.98 ;
2022-11-23 19:07:17 +09:00
const changed = isMobile !== this . isMobile ;
this . isMobile = isMobile ;
2024-05-19 00:39:33 +07:00
this . paginationMaxSize = target . innerWidth < 670 ? 3 : 5 ;
2022-11-23 19:07:17 +09:00
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 ) ;
2024-03-08 18:24:18 -08:00
2025-01-06 18:41:46 +00:00
const queryParams = { . . . this . currentQueryParams } ;
delete queryParams [ 'audit' ] ;
2024-03-08 18:24:18 -08:00
2025-01-06 18:41:46 +00:00
let newUrl = this . router . url . split ( '?' ) [ 0 ] ;
const queryString = new URLSearchParams ( queryParams ) . toString ( ) ;
if ( queryString ) {
newUrl += '?' + queryString ;
}
this . location . replaceState ( newUrl ) ;
2024-03-08 18:24:18 -08:00
2025-01-06 18:41:46 +00:00
// avoid duplicate subscriptions
this . auditPrefSubscription ? . unsubscribe ( ) ;
2024-03-08 18:24:18 -08:00
this . auditPrefSubscription = this . stateService . hideAudit . subscribe ( ( hide ) = > {
this . auditModeEnabled = ! hide ;
this . showAudit = this . auditAvailable && 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
2024-03-08 18:24:18 -08:00
isAuditEnabledFromParam ( ) : Observable < boolean > {
return this . route . queryParams . pipe (
map ( params = > {
this . auditParamEnabled = 'audit' in params ;
2025-01-06 18:41:46 +00:00
2024-03-08 18:24:18 -08:00
return this . auditParamEnabled ? ! ( params [ 'audit' ] === 'false' ) : true ;
} )
) ;
}
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
}
2024-05-22 16:16:47 +07:00
updateBlockReward ( blockReward : number ) : void {
if ( this . fees === undefined ) {
this . fees = blockReward ;
}
}
2024-10-22 21:05:01 +09:00
}