From 8fee1955771cd2f5dd5ed13ce970ec2349bd496d Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Thu, 21 Mar 2024 16:44:07 +0900 Subject: [PATCH 01/44] [accelerator] prepaid acceleration --- .../accelerate-preview.component.html | 12 +- .../accelerate-preview.component.ts | 245 +++++++++++++----- .../master-page/master-page.component.ts | 2 - .../src/app/services/services-api.service.ts | 8 + 4 files changed, 205 insertions(+), 62 deletions(-) diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html index a848a645b..34881eba5 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html @@ -219,7 +219,7 @@ - + @@ -242,13 +242,21 @@ -
+
+ +
+
+
+ Loading +
+
+
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index 3e8dbb6ff..68763fbbe 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -1,5 +1,4 @@ import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core'; -import { ApiService } from '../../services/api.service'; import { Subscription, catchError, of, tap } from 'rxjs'; import { StorageService } from '../../services/storage.service'; import { Transaction } from '../../interfaces/electrs.interface'; @@ -40,7 +39,7 @@ export const MAX_BID_RATIO = 4; templateUrl: 'accelerate-preview.component.html', styleUrls: ['accelerate-preview.component.scss'] }) -export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges { +export class AcceleratePreviewComponent implements OnDestroy, OnChanges { @Input() tx: Transaction | undefined; @Input() scrollEvent: boolean; @@ -63,18 +62,37 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges maxRateOptions: RateOption[] = []; + // Cashapp payment + paymentType: 'bitcoin' | 'cashapp' = 'bitcoin'; + cashAppSubscription: Subscription; + conversionsSubscription: Subscription; + payments: any; + showSpinner = false; + square: any; + cashAppPay: any; + hideCashApp = false; + constructor( public stateService: StateService, private servicesApiService: ServicesApiServices, private storageService: StorageService, private audioService: AudioService, private cd: ChangeDetectorRef - ) { } + ) { + if (window.document.referrer === 'cash.app') { + this.paymentType = 'cashapp'; + } else { + this.paymentType = 'bitcoin'; + } + } ngOnDestroy(): void { if (this.estimateSubscription) { this.estimateSubscription.unsubscribe(); } + if (this.cashAppPay) { + this.cashAppPay.destroy(); + } } ngOnChanges(changes: SimpleChanges): void { @@ -83,69 +101,85 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges } } - ngOnInit() { + ngAfterViewInit() { + this.showSpinner = true; + this.user = this.storageService.getAuth()?.user ?? null; - this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe( - tap((response) => { - if (response.status === 204) { - this.estimate = undefined; - this.error = `cannot_accelerate_tx`; - this.scrollToPreviewWithTimeout('mempoolError', 'center'); - this.estimateSubscription.unsubscribe(); - } else { - this.estimate = response.body; - if (!this.estimate) { + this.servicesApiService.setupSquare$().subscribe(ids => { + this.square = { + appId: ids.squareAppId, + locationId: ids.squareLocationId + }; + this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe( + tap((response) => { + if (response.status === 204) { + this.estimate = undefined; this.error = `cannot_accelerate_tx`; this.scrollToPreviewWithTimeout('mempoolError', 'center'); this.estimateSubscription.unsubscribe(); - } - - if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) { - if (this.isLoggedIn()) { - this.error = `not_enough_balance`; + } else { + this.estimate = response.body; + if (!this.estimate) { + this.error = `cannot_accelerate_tx`; this.scrollToPreviewWithTimeout('mempoolError', 'center'); + this.estimateSubscription.unsubscribe(); + } + + if (this.paymentType === 'cashapp') { + this.estimate.userBalance = 999999999; + this.estimate.enoughBalance = true; + } + + if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) { + if (this.isLoggedIn()) { + this.error = `not_enough_balance`; + this.scrollToPreviewWithTimeout('mempoolError', 'center'); + } + } + + this.hasAncestors = this.estimate.txSummary.ancestorCount > 1; + + // Make min extra fee at least 50% of the current tx fee + this.minExtraCost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee)); + + this.maxRateOptions = [1, 2, 4].map((multiplier, index) => { + return { + fee: this.minExtraCost * multiplier, + rate: (this.estimate.txSummary.effectiveFee + (this.minExtraCost * multiplier)) / this.estimate.txSummary.effectiveVsize, + index, + }; + }); + + this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO; + this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO; + this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO; + + this.userBid = this.defaultBid; + if (this.userBid < this.minBidAllowed) { + this.userBid = this.minBidAllowed; + } else if (this.userBid > this.maxBidAllowed) { + this.userBid = this.maxBidAllowed; + } + this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee; + + if (!this.error) { + this.scrollToPreview('acceleratePreviewAnchor', 'start'); + if (this.paymentType === 'cashapp') { + this.setupSquare(); + } } } - - this.hasAncestors = this.estimate.txSummary.ancestorCount > 1; - - // Make min extra fee at least 50% of the current tx fee - this.minExtraCost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee)); - - this.maxRateOptions = [1, 2, 4].map((multiplier, index) => { - return { - fee: this.minExtraCost * multiplier, - rate: (this.estimate.txSummary.effectiveFee + (this.minExtraCost * multiplier)) / this.estimate.txSummary.effectiveVsize, - index, - }; - }); - - this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO; - this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO; - this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO; - - this.userBid = this.defaultBid; - if (this.userBid < this.minBidAllowed) { - this.userBid = this.minBidAllowed; - } else if (this.userBid > this.maxBidAllowed) { - this.userBid = this.maxBidAllowed; - } - this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee; - - if (!this.error) { - this.scrollToPreview('acceleratePreviewAnchor', 'start'); - } - } - }), - catchError((response) => { - this.estimate = undefined; - this.error = response.error; - this.scrollToPreviewWithTimeout('mempoolError', 'center'); - this.estimateSubscription.unsubscribe(); - return of(null); - }) - ).subscribe(); + }), + catchError((response) => { + this.estimate = undefined; + this.error = response.error; + this.scrollToPreviewWithTimeout('mempoolError', 'center'); + this.estimateSubscription.unsubscribe(); + return of(null); + }) + ).subscribe(); + }); } /** @@ -216,4 +250,99 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges onResize(): void { this.isMobile = window.innerWidth <= 767.98; } + + /** + * CashApp payment + */ + setupSquare() { + const init = () => { + this.initSquare(); + }; + + //@ts-ignore + if (!window.Square) { + console.warn('Square.js failed to load properly. Retrying in 1 second.'); + setTimeout(init, 1000); + } else { + init(); + } + } + + async initSquare(): Promise { + try { + //@ts-ignore + this.payments = window.Square.payments(this.square.appId, this.square.locationId) + await this.requestCashAppPayment(); + } catch (e) { + console.error(e); + this.error = 'Error loading Square Payments'; + return; + } + } + + async requestCashAppPayment() { + if (this.cashAppSubscription) { + this.cashAppSubscription.unsubscribe(); + } + if (this.conversionsSubscription) { + this.conversionsSubscription.unsubscribe(); + } + this.hideCashApp = false; + + + this.conversionsSubscription = this.stateService.conversions$.subscribe( + async (conversions) => { + const maxCostUsd = this.maxCost / 100_000_000 * conversions.USD; + const paymentRequest = this.payments.paymentRequest({ + countryCode: 'US', + currencyCode: 'USD', + total: { + amount: maxCostUsd.toString(), + label: 'Total', + pending: true, + productUrl: `https://mempool.space/tx/${this.tx.txid}`, + } + }); + this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { + redirectURL: 'https://my.website/checkout', + referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, + }); + await this.cashAppPay.attach('#cash-app-pay'); + this.showSpinner = false; + + const that = this; + this.cashAppPay.addEventListener('ontokenization', function (event) { + const { tokenResult, error } = event.detail; + if (error) { + this.error = error; + } else if (tokenResult.status === 'OK') { + that.hideCashApp = true; + + that.accelerationSubscription = that.servicesApiService.accelerateWithCashApp$( + that.tx.txid, + that.userBid, + tokenResult.token, + tokenResult.details.cashAppPay.cashtag, + tokenResult.details.cashAppPay.referenceId + ).subscribe({ + next: () => { + that.audioService.playSound('ascend-chime-cartoon'); + that.showSuccess = true; + that.scrollToPreviewWithTimeout('successAlert', 'center'); + that.estimateSubscription.unsubscribe(); + }, + error: (response) => { + if (response.status === 403 && response.error === 'not_available') { + that.error = 'waitlisted'; + } else { + that.error = response.error; + } + that.scrollToPreviewWithTimeout('mempoolError', 'center'); + } + }); + } + }); + } + ); + } } diff --git a/frontend/src/app/components/master-page/master-page.component.ts b/frontend/src/app/components/master-page/master-page.component.ts index 6f376f923..f3472f204 100644 --- a/frontend/src/app/components/master-page/master-page.component.ts +++ b/frontend/src/app/components/master-page/master-page.component.ts @@ -7,7 +7,6 @@ import { EnterpriseService } from '../../services/enterprise.service'; import { NavigationService } from '../../services/navigation.service'; import { MenuComponent } from '../menu/menu.component'; import { StorageService } from '../../services/storage.service'; -import { ApiService } from '../../services/api.service'; @Component({ selector: 'app-master-page', @@ -45,7 +44,6 @@ export class MasterPageComponent implements OnInit, OnDestroy { private enterpriseService: EnterpriseService, private navigationService: NavigationService, private storageService: StorageService, - private apiService: ApiService, private router: Router, ) { } diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index ad8af5536..e2cef2cc1 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -141,6 +141,10 @@ export class ServicesApiServices { return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid }); } + accelerateWithCashApp$(txInput: string, userBid: number, token: string, cashtag: string, referenceId: string) { + return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, userBid: userBid, token: token, cashtag: cashtag, referenceId: referenceId }); + } + getAccelerations$(): Observable { return this.httpClient.get(`${SERVICES_API_PREFIX}/accelerator/accelerations`); } @@ -160,4 +164,8 @@ export class ServicesApiServices { getAccelerationStats$(): Observable { return this.httpClient.get(`${SERVICES_API_PREFIX}/accelerator/accelerations/stats`); } + + setupSquare$(): Observable<{squareAppId: string, squareLocationId: string}> { + return this.httpClient.get<{squareAppId: string, squareLocationId: string}>(`${SERVICES_API_PREFIX}/square/setup`); + } } From 1eb52d8a355e8125964c641df257416bf492c5b1 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Thu, 21 Mar 2024 19:08:48 +0900 Subject: [PATCH 02/44] [accelerator] fix redirection link --- .../accelerate-preview/accelerate-preview.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index 68763fbbe..83eb7c678 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -304,7 +304,7 @@ export class AcceleratePreviewComponent implements OnDestroy, OnChanges { } }); this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { - redirectURL: 'https://my.website/checkout', + redirectURL: `https://mempool.space/tx/${this.tx.txid}`, referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, }); await this.cashAppPay.attach('#cash-app-pay'); From 60040c3914d194cfbe5e4a4b9628b70b12669759 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Thu, 4 Apr 2024 12:57:54 +0900 Subject: [PATCH 03/44] [accelerator] proxy acceleration api to prod --- .../api/acceleration/acceleration.routes.ts | 63 +++++++++++++++++++ .../api/{ => acceleration}/acceleration.ts | 6 +- backend/src/index.ts | 4 ++ .../repositories/AccelerationRepository.ts | 4 +- 4 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 backend/src/api/acceleration/acceleration.routes.ts rename backend/src/api/{ => acceleration}/acceleration.ts (99%) diff --git a/backend/src/api/acceleration/acceleration.routes.ts b/backend/src/api/acceleration/acceleration.routes.ts new file mode 100644 index 000000000..d5c3844ec --- /dev/null +++ b/backend/src/api/acceleration/acceleration.routes.ts @@ -0,0 +1,63 @@ +import { Application, Request, Response } from "express"; +import config from "../../config"; +import axios from "axios"; +import logger from "../../logger"; + +class AccelerationRoutes { + private tag = 'Accelerator'; + + public initRoutes(app: Application) { + app + .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations', this.$getAcceleratorAccelerations.bind(this)) + .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history', this.$getAcceleratorAccelerationsHistory.bind(this)) + .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history/aggregated', this.$getAcceleratorAccelerationsHistoryAggregated.bind(this)) + .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/stats', this.$getAcceleratorAccelerationsStats.bind(this)) + ; + } + + private async $getAcceleratorAccelerations(req: Request, res: Response) { + const url = `https://mempool.space${req.originalUrl}`; + try { + const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + response.data.pipe(res); + } catch (e) { + logger.err(`Unable to get current accelerations from ${url} in $getAcceleratorAccelerations(), ${e}`, this.tag); + res.status(500).end(); + } + } + + private async $getAcceleratorAccelerationsHistory(req: Request, res: Response) { + const url = `https://mempool.space${req.originalUrl}`; + try { + const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + response.data.pipe(res); + } catch (e) { + logger.err(`Unable to get acceleration history from ${url} in $getAcceleratorAccelerationsHistory(), ${e}`, this.tag); + res.status(500).end(); + } + } + + private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response) { + const url = `https://mempool.space${req.originalUrl}`; + try { + const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + response.data.pipe(res); + } catch (e) { + logger.err(`Unable to get aggregated acceleration history from ${url} in $getAcceleratorAccelerationsHistoryAggregated(), ${e}`, this.tag); + res.status(500).end(); + } + } + + private async $getAcceleratorAccelerationsStats(req: Request, res: Response) { + const url = `https://mempool.space${req.originalUrl}`; + try { + const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + response.data.pipe(res); + } catch (e) { + logger.err(`Unable to get acceleration stats from ${url} in $getAcceleratorAccelerationsStats(), ${e}`, this.tag); + res.status(500).end(); + } + } +} + +export default new AccelerationRoutes(); \ No newline at end of file diff --git a/backend/src/api/acceleration.ts b/backend/src/api/acceleration/acceleration.ts similarity index 99% rename from backend/src/api/acceleration.ts rename to backend/src/api/acceleration/acceleration.ts index 412d65231..2dbaa8b07 100644 --- a/backend/src/api/acceleration.ts +++ b/backend/src/api/acceleration/acceleration.ts @@ -1,6 +1,6 @@ -import logger from '../logger'; -import { MempoolTransactionExtended } from '../mempool.interfaces'; -import { IEsploraApi } from './bitcoin/esplora-api.interface'; +import logger from '../../logger'; +import { MempoolTransactionExtended } from '../../mempool.interfaces'; +import { IEsploraApi } from '../bitcoin/esplora-api.interface'; const BLOCK_WEIGHT_UNITS = 4_000_000; const BLOCK_SIGOPS = 80_000; diff --git a/backend/src/index.ts b/backend/src/index.ts index b28e9ccbf..78583451c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -43,6 +43,7 @@ import redisCache from './api/redis-cache'; import accelerationApi from './api/services/acceleration'; import bitcoinCoreRoutes from './api/bitcoin/bitcoin-core.routes'; import bitcoinSecondClient from './api/bitcoin/bitcoin-second-client'; +import accelerationRoutes from './api/acceleration/acceleration.routes'; class Server { private wss: WebSocket.Server | undefined; @@ -305,6 +306,9 @@ class Server { nodesRoutes.initRoutes(this.app); channelsRoutes.initRoutes(this.app); } + if (config.MEMPOOL_SERVICES.ACCELERATIONS) { + accelerationRoutes.initRoutes(this.app); + } } healthCheck(): void { diff --git a/backend/src/repositories/AccelerationRepository.ts b/backend/src/repositories/AccelerationRepository.ts index 1c91df050..4969013c4 100644 --- a/backend/src/repositories/AccelerationRepository.ts +++ b/backend/src/repositories/AccelerationRepository.ts @@ -1,4 +1,4 @@ -import { AccelerationInfo, makeBlockTemplate } from '../api/acceleration'; +import { AccelerationInfo, makeBlockTemplate } from '../api/acceleration/acceleration'; import { RowDataPacket } from 'mysql2'; import DB from '../database'; import logger from '../logger'; @@ -7,7 +7,7 @@ import { Common } from '../api/common'; import config from '../config'; import blocks from '../api/blocks'; import accelerationApi, { Acceleration } from '../api/services/acceleration'; -import accelerationCosts from '../api/acceleration'; +import accelerationCosts from '../api/acceleration/acceleration'; import bitcoinApi from '../api/bitcoin/bitcoin-api-factory'; import transactionUtils from '../api/transaction-utils'; import { BlockExtended, MempoolTransactionExtended } from '../mempool.interfaces'; From 404079ef4ecf386e227d6af22363cea8e8b594e6 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Thu, 4 Apr 2024 13:10:32 +0900 Subject: [PATCH 04/44] [accelerator] use config.MEMPOOL_SERVICES.API url --- backend/src/api/acceleration/acceleration.routes.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/api/acceleration/acceleration.routes.ts b/backend/src/api/acceleration/acceleration.routes.ts index d5c3844ec..ed53ec5e5 100644 --- a/backend/src/api/acceleration/acceleration.routes.ts +++ b/backend/src/api/acceleration/acceleration.routes.ts @@ -16,7 +16,7 @@ class AccelerationRoutes { } private async $getAcceleratorAccelerations(req: Request, res: Response) { - const url = `https://mempool.space${req.originalUrl}`; + const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -27,7 +27,7 @@ class AccelerationRoutes { } private async $getAcceleratorAccelerationsHistory(req: Request, res: Response) { - const url = `https://mempool.space${req.originalUrl}`; + const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -38,7 +38,7 @@ class AccelerationRoutes { } private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response) { - const url = `https://mempool.space${req.originalUrl}`; + const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -49,7 +49,7 @@ class AccelerationRoutes { } private async $getAcceleratorAccelerationsStats(req: Request, res: Response) { - const url = `https://mempool.space${req.originalUrl}`; + const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); From b421be33155d41beb1431ad4b21ac8e1a92f71fd Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Thu, 4 Apr 2024 14:04:12 +0900 Subject: [PATCH 05/44] [accelerator] also forward headers --- backend/src/api/acceleration/acceleration.routes.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/src/api/acceleration/acceleration.routes.ts b/backend/src/api/acceleration/acceleration.routes.ts index ed53ec5e5..69b320171 100644 --- a/backend/src/api/acceleration/acceleration.routes.ts +++ b/backend/src/api/acceleration/acceleration.routes.ts @@ -19,6 +19,9 @@ class AccelerationRoutes { const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + for (const key in response.headers) { + res.setHeader(key, response.headers[key]); + } response.data.pipe(res); } catch (e) { logger.err(`Unable to get current accelerations from ${url} in $getAcceleratorAccelerations(), ${e}`, this.tag); @@ -30,6 +33,9 @@ class AccelerationRoutes { const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + for (const key in response.headers) { + res.setHeader(key, response.headers[key]); + } response.data.pipe(res); } catch (e) { logger.err(`Unable to get acceleration history from ${url} in $getAcceleratorAccelerationsHistory(), ${e}`, this.tag); @@ -41,6 +47,9 @@ class AccelerationRoutes { const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + for (const key in response.headers) { + res.setHeader(key, response.headers[key]); + } response.data.pipe(res); } catch (e) { logger.err(`Unable to get aggregated acceleration history from ${url} in $getAcceleratorAccelerationsHistoryAggregated(), ${e}`, this.tag); @@ -52,6 +61,9 @@ class AccelerationRoutes { const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + for (const key in response.headers) { + res.setHeader(key, response.headers[key]); + } response.data.pipe(res); } catch (e) { logger.err(`Unable to get acceleration stats from ${url} in $getAcceleratorAccelerationsStats(), ${e}`, this.tag); From abdb27af3f45f15c43fb5e43980a79854eb83480 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Thu, 4 Apr 2024 14:27:50 +0900 Subject: [PATCH 06/44] [migration] reset mining pool sha to force refreshing --- backend/src/api/database-migration.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 278cd2631..81f2caa44 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -655,6 +655,7 @@ class DatabaseMigration { await this.$executeQuery('TRUNCATE hashrates'); await this.$executeQuery('TRUNCATE difficulty_adjustments'); + await this.$executeQuery(`UPDATE state SET string = NULL WHERE name = 'pools_json_sha'`); await this.updateToSchemaVersion(75); } From dcf78fab068181d267b9f6996e878822611d1b58 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 03:54:11 +0000 Subject: [PATCH 07/44] Block visualization color-by-age mode --- frontend/src/app/app.constants.ts | 2 + .../block-filters.component.html | 31 ++++++--- .../block-filters.component.scss | 13 ++++ .../block-filters/block-filters.component.ts | 20 ++++-- .../block-overview-graph.component.ts | 35 +++++----- .../block-overview-graph/block-scene.ts | 4 ++ .../components/block-overview-graph/utils.ts | 65 ++++++++++++++----- .../src/app/dashboard/dashboard.component.ts | 15 +++-- frontend/src/app/services/state.service.ts | 2 +- frontend/src/app/shared/filters.utils.ts | 3 + 10 files changed, 135 insertions(+), 55 deletions(-) diff --git a/frontend/src/app/app.constants.ts b/frontend/src/app/app.constants.ts index 17105d97e..4ded1b766 100644 --- a/frontend/src/app/app.constants.ts +++ b/frontend/src/app/app.constants.ts @@ -39,6 +39,8 @@ export const mempoolFeeColors = [ 'ae005b', ]; +export const mempoolAgeColors = [ '28007d', '21017d', '1c027d', '14047d', '0d057d', '06067d', '081186', '09188c', '0b2395', '0c2a9b', '0e37a6', '0f3caa', '1045b2', '114fbb', '1254bf', '155cbf', '1965bf', '1e70be', '2076be', '2581bd', '2889bd', '2d94bc', '309dbc', '34a6bc', '39b1bb', '3cbabb', '37bbb3', '32baa9', '2bb99c', '25b993', '21b88c', '1db785', '19b67e', '14b475', '0eb36c', '08b162', '02b059', '00ae53']; + export const chartColors = [ "#D81B60", "#8E24AA", diff --git a/frontend/src/app/components/block-filters/block-filters.component.html b/frontend/src/app/components/block-filters/block-filters.component.html index 8c79cd438..093ddc11a 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -14,14 +14,29 @@
-
Match
-
- - +
+
+
Match
+
+ + +
+
+
+
Gradient
+
+ + +
+
{{ group.label }}
diff --git a/frontend/src/app/components/block-filters/block-filters.component.scss b/frontend/src/app/components/block-filters/block-filters.component.scss index 1009efd72..b1c4bce17 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.scss +++ b/frontend/src/app/components/block-filters/block-filters.component.scss @@ -45,6 +45,13 @@ } .filter-menu { + .filter-row { + display: flex; + flex-direction: row; + justify-content: start; + align-items: baseline; + } + h5 { font-size: 0.8rem; color: white; @@ -118,6 +125,12 @@ background: #1a9436; } } + &.yellow { + border: solid 1px #bf7815; + &.active { + background: #bf7815; + } + } } :host-context(.block-overview-graph:hover) &, &:hover, &:active { diff --git a/frontend/src/app/components/block-filters/block-filters.component.ts b/frontend/src/app/components/block-filters/block-filters.component.ts index a16475c23..7f997617c 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.ts +++ b/frontend/src/app/components/block-filters/block-filters.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core'; -import { ActiveFilter, FilterGroups, FilterMode, TransactionFilters } from '../../shared/filters.utils'; +import { ActiveFilter, FilterGroups, FilterMode, GradientMode, TransactionFilters } from '../../shared/filters.utils'; import { StateService } from '../../services/state.service'; import { Subscription } from 'rxjs'; @@ -22,6 +22,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { activeFilters: string[] = []; filterFlags: { [key: string]: boolean } = {}; filterMode: FilterMode = 'and'; + gradientMode: GradientMode = 'fee'; menuOpen: boolean = false; constructor( @@ -32,6 +33,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { ngOnInit(): void { this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => { this.filterMode = active.mode; + this.gradientMode = active.gradient; for (const key of Object.keys(this.filterFlags)) { this.filterFlags[key] = false; } @@ -39,7 +41,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { this.filterFlags[key] = !this.disabledFilters[key]; } this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])]; - this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters }); + this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters, gradient: this.gradientMode }); }); } @@ -57,8 +59,14 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { setFilterMode(mode): void { this.filterMode = mode; - this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters }); - this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] }); + this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode }); + this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode }); + } + + setGradientMode(mode): void { + this.gradientMode = mode; + this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode }); + this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode }); } toggleFilter(key): void { @@ -81,8 +89,8 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { this.activeFilters = this.activeFilters.filter(f => f != key); } const booleanFlags = this.getBooleanFlags(); - this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters }); - this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] }); + this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode }); + this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode }); } getBooleanFlags(): bigint | null { diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index 003531fce..16e482cc8 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -8,14 +8,11 @@ import { Color, Position } from './sprite-types'; import { Price } from '../../services/price.service'; import { StateService } from '../../services/state.service'; import { Subscription } from 'rxjs'; -import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils'; +import { defaultColorFunction, setOpacity, defaultAuditColors, defaultColors } from './utils'; import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils'; import { detectWebGL } from '../../shared/graphs.utils'; const unmatchedOpacity = 0.2; -const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity)); -const unmatchedAuditFeeColors = defaultAuditFeeColors.map(c => setOpacity(c, unmatchedOpacity)); -const unmatchedMarginalFeeColors = defaultMarginalFeeColors.map(c => setOpacity(c, unmatchedOpacity)); const unmatchedAuditColors = { censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity), missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity), @@ -46,6 +43,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On @Input() excludeFilters: string[] = []; @Input() filterFlags: bigint | null = null; @Input() filterMode: FilterMode = 'and'; + @Input() gradientMode: 'fee' | 'age' = 'fee'; @Input() relativeTime: number | null; @Input() blockConversion: Price; @Input() overrideColors: ((tx: TxView) => Color) | null = null; @@ -121,21 +119,22 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On this.setHighlightingEnabled(this.auditHighlighting); } if (changes.overrideColor && this.scene) { - this.scene.setColorFunction(this.overrideColors); + this.scene.setColorFunction(this.getFilterColorFunction(0n, this.gradientMode)); } - if ((changes.filterFlags || changes.showFilters || changes.filterMode)) { + if ((changes.filterFlags || changes.showFilters || changes.filterMode || changes.gradientMode)) { this.setFilterFlags(); } } setFilterFlags(goggle?: ActiveFilter): void { this.filterMode = goggle?.mode || this.filterMode; + this.gradientMode = goggle?.gradient || this.gradientMode; this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags; if (this.scene) { if (this.activeFilterFlags != null && this.filtersAvailable) { - this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags)); + this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags, this.gradientMode)); } else { - this.scene.setColorFunction(this.overrideColors); + this.scene.setColorFunction(this.getFilterColorFunction(0n, this.gradientMode)); } } this.start(); @@ -212,6 +211,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On remove = remove.filter(txid => this.scene.txs[txid]); change = change.filter(tx => this.scene.txs[tx.txid]); + if (this.gradientMode === 'age') { + this.scene.updateAllColors(); + } this.scene.update(add, remove, change, direction, resetLayout); this.start(); this.updateSearchHighlight(); @@ -548,25 +550,24 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On getColorFunction(): ((tx: TxView) => Color) { if (this.filterFlags) { - return this.getFilterColorFunction(this.filterFlags); + return this.getFilterColorFunction(this.filterFlags, this.gradientMode); } else if (this.activeFilterFlags) { - return this.getFilterColorFunction(this.activeFilterFlags); + return this.getFilterColorFunction(this.activeFilterFlags, this.gradientMode); } else { - return this.overrideColors; + return this.getFilterColorFunction(0n, this.gradientMode); } } - getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) { + getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) { return (tx: TxView) => { if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) { - return defaultColorFunction(tx); + return defaultColorFunction(tx, defaultColors[gradient], defaultAuditColors, this.relativeTime || (Date.now() / 1000)); } else { return defaultColorFunction( tx, - unmatchedFeeColors, - unmatchedAuditFeeColors, - unmatchedMarginalFeeColors, - unmatchedAuditColors + defaultColors['unmatched' + gradient], + unmatchedAuditColors, + this.relativeTime || (Date.now() / 1000) ); } }; diff --git a/frontend/src/app/components/block-overview-graph/block-scene.ts b/frontend/src/app/components/block-overview-graph/block-scene.ts index 5d2196f1e..fb45e492b 100644 --- a/frontend/src/app/components/block-overview-graph/block-scene.ts +++ b/frontend/src/app/components/block-overview-graph/block-scene.ts @@ -68,6 +68,10 @@ export default class BlockScene { setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void { this.getColor = colorFunction || defaultColorFunction; + this.updateAllColors(); + } + + updateAllColors(): void { this.dirty = true; if (this.initialised && this.scene) { this.updateColors(performance.now(), 50); diff --git a/frontend/src/app/components/block-overview-graph/utils.ts b/frontend/src/app/components/block-overview-graph/utils.ts index b6c8ccf5e..494bd7756 100644 --- a/frontend/src/app/components/block-overview-graph/utils.ts +++ b/frontend/src/app/components/block-overview-graph/utils.ts @@ -1,4 +1,4 @@ -import { feeLevels, mempoolFeeColors } from '../../app.constants'; +import { feeLevels, mempoolAgeColors, mempoolFeeColors } from '../../app.constants'; import { Color } from './sprite-types'; import TxView from './tx-view'; @@ -37,10 +37,42 @@ export function setOpacity(color: Color, opacity: number): Color { }; } +interface ColorPalette { + base: Color[], + audit: Color[], + marginal: Color[], + baseLevel: (tx: TxView, rate: number, time: number) => number, +} + // precomputed colors -export const defaultFeeColors = mempoolFeeColors.map(hexToColor); -export const defaultAuditFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.3), 0.9)); -export const defaultMarginalFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.8), 1.1)); +const defaultColors: { [key: string]: ColorPalette } = { + fee: { + base: mempoolFeeColors.map(hexToColor), + audit: [], + marginal: [], + baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1 + }, + age: { + base: mempoolAgeColors.map(hexToColor), + audit: [], + marginal: [], + baseLevel: (tx: TxView, rate: number, relativeTime: number) => (!tx.time ? 0 : Math.max(0, Math.round(1.25 * Math.log2((Math.max(1, relativeTime - tx.time)))))) + }, +} +for (const key in defaultColors) { + const base = defaultColors[key].base; + defaultColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9)); + defaultColors[key].marginal = base.map((color) => darken(desaturate(color, 0.8), 1.1)); + defaultColors['unmatched' + key] = { + base: defaultColors[key].base.map(c => setOpacity(c, 0.2)), + audit: defaultColors[key].audit.map(c => setOpacity(c, 0.2)), + marginal: defaultColors[key].marginal.map(c => setOpacity(c, 0.2)), + baseLevel: defaultColors[key].baseLevel, + }; +} + +export { defaultColors as defaultColors }; + export const defaultAuditColors = { censored: hexToColor('f344df'), missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7), @@ -51,22 +83,21 @@ export const defaultAuditColors = { export function defaultColorFunction( tx: TxView, - feeColors: Color[] = defaultFeeColors, - auditFeeColors: Color[] = defaultAuditFeeColors, - marginalFeeColors: Color[] = defaultMarginalFeeColors, - auditColors: { [status: string]: Color } = defaultAuditColors + colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee, + auditColors: { [status: string]: Color } = defaultAuditColors, + relativeTime?: number, ): Color { const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate - const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1; - const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1]; + const levelIndex = colors.baseLevel(tx, rate, relativeTime || (Date.now() / 1000)); + const levelColor = colors.base[levelIndex] || colors.base[mempoolFeeColors.length - 1]; // Normal mode if (!tx.scene?.highlightingEnabled) { if (tx.acc) { return auditColors.accelerated; } else { - return feeLevelColor; + return levelColor; } - return feeLevelColor; + return levelColor; } // Block audit switch(tx.status) { @@ -75,7 +106,7 @@ export function defaultColorFunction( case 'missing': case 'sigop': case 'rbf': - return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; + return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1]; case 'fresh': case 'freshcpfp': return auditColors.missing; @@ -84,20 +115,20 @@ export function defaultColorFunction( case 'prioritized': return auditColors.prioritized; case 'selected': - return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; + return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1]; case 'accelerated': return auditColors.accelerated; case 'found': if (tx.context === 'projected') { - return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1]; + return colors.audit[levelIndex] || colors.audit[mempoolFeeColors.length - 1]; } else { - return feeLevelColor; + return levelColor; } default: if (tx.acc) { return auditColors.accelerated; } else { - return feeLevelColor; + return levelColor; } } } \ No newline at end of file diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index f396ba6ae..5f0eea62d 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -7,7 +7,7 @@ import { ApiService } from '../services/api.service'; import { StateService } from '../services/state.service'; import { WebsocketService } from '../services/websocket.service'; import { SeoService } from '../services/seo.service'; -import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils'; +import { ActiveFilter, FilterMode, GradientMode, toFlags } from '../shared/filters.utils'; import { detectWebGL } from '../shared/graphs.utils'; interface MempoolBlocksData { @@ -74,14 +74,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { private lastReservesBlockUpdate: number = 0; goggleResolution = 82; - goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[] }[] = [ - { index: 0, name: 'All', mode: 'and', filters: [] }, - { index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] }, - { index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] }, - { index: 3, name: 'Data', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'] }, + goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[], gradient: GradientMode }[] = [ + { index: 0, name: 'All', mode: 'and', filters: [], gradient: 'fee' }, + { index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'], gradient: 'fee' }, + { index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'], gradient: 'fee' }, + { index: 3, name: 'Data', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'], gradient: 'fee' }, ]; goggleFlags = 0n; goggleMode: FilterMode = 'and'; + gradientMode: GradientMode = 'fee'; goggleIndex = 0; private destroy$ = new Subject(); @@ -131,6 +132,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { this.goggleIndex = goggle.index; this.goggleFlags = toFlags(goggle.filters); this.goggleMode = goggle.mode; + this.gradientMode = goggle.gradient; return; } } @@ -140,6 +142,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { name: 'Custom', mode: active.mode, filters: active.filters, + gradient: active.gradient, }); this.goggleIndex = this.goggleCycle.length - 1; this.goggleFlags = toFlags(active.filters); diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 12caf9f53..4ea574c8e 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -154,7 +154,7 @@ export class StateService { searchFocus$: Subject = new Subject(); menuOpen$: BehaviorSubject = new BehaviorSubject(false); - activeGoggles$: BehaviorSubject = new BehaviorSubject({ mode: 'and', filters: [] }); + activeGoggles$: BehaviorSubject = new BehaviorSubject({ mode: 'and', filters: [], gradient: 'fee' }); constructor( @Inject(PLATFORM_ID) private platformId: any, diff --git a/frontend/src/app/shared/filters.utils.ts b/frontend/src/app/shared/filters.utils.ts index ab99e00ce..5df6c4818 100644 --- a/frontend/src/app/shared/filters.utils.ts +++ b/frontend/src/app/shared/filters.utils.ts @@ -11,9 +11,12 @@ export interface Filter { export type FilterMode = 'and' | 'or'; +export type GradientMode = 'fee' | 'age'; + export interface ActiveFilter { mode: FilterMode, filters: string[], + gradient: GradientMode, } // binary flags for transaction classification From 26227e2f3bb871d008d9471d610faed444a2970a Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 4 Apr 2024 06:56:37 +0000 Subject: [PATCH 08/44] New opacity-based age Goggles --- frontend/src/app/app.constants.ts | 2 -- .../block-overview-graph.component.ts | 8 +++--- .../components/block-overview-graph/utils.ts | 25 +++++++++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/app.constants.ts b/frontend/src/app/app.constants.ts index 4ded1b766..17105d97e 100644 --- a/frontend/src/app/app.constants.ts +++ b/frontend/src/app/app.constants.ts @@ -39,8 +39,6 @@ export const mempoolFeeColors = [ 'ae005b', ]; -export const mempoolAgeColors = [ '28007d', '21017d', '1c027d', '14047d', '0d057d', '06067d', '081186', '09188c', '0b2395', '0c2a9b', '0e37a6', '0f3caa', '1045b2', '114fbb', '1254bf', '155cbf', '1965bf', '1e70be', '2076be', '2581bd', '2889bd', '2d94bc', '309dbc', '34a6bc', '39b1bb', '3cbabb', '37bbb3', '32baa9', '2bb99c', '25b993', '21b88c', '1db785', '19b67e', '14b475', '0eb36c', '08b162', '02b059', '00ae53']; - export const chartColors = [ "#D81B60", "#8E24AA", diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index 16e482cc8..018a0c85a 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -8,7 +8,7 @@ import { Color, Position } from './sprite-types'; import { Price } from '../../services/price.service'; import { StateService } from '../../services/state.service'; import { Subscription } from 'rxjs'; -import { defaultColorFunction, setOpacity, defaultAuditColors, defaultColors } from './utils'; +import { defaultColorFunction, setOpacity, defaultAuditColors, defaultColors, ageColorFunction } from './utils'; import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils'; import { detectWebGL } from '../../shared/graphs.utils'; @@ -561,11 +561,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) { return (tx: TxView) => { if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) { - return defaultColorFunction(tx, defaultColors[gradient], defaultAuditColors, this.relativeTime || (Date.now() / 1000)); + return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)); } else { - return defaultColorFunction( + return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction( tx, - defaultColors['unmatched' + gradient], + defaultColors.unmatchedfee, unmatchedAuditColors, this.relativeTime || (Date.now() / 1000) ); diff --git a/frontend/src/app/components/block-overview-graph/utils.ts b/frontend/src/app/components/block-overview-graph/utils.ts index 494bd7756..568cd5ad6 100644 --- a/frontend/src/app/components/block-overview-graph/utils.ts +++ b/frontend/src/app/components/block-overview-graph/utils.ts @@ -1,4 +1,4 @@ -import { feeLevels, mempoolAgeColors, mempoolFeeColors } from '../../app.constants'; +import { feeLevels, mempoolFeeColors } from '../../app.constants'; import { Color } from './sprite-types'; import TxView from './tx-view'; @@ -52,12 +52,6 @@ const defaultColors: { [key: string]: ColorPalette } = { marginal: [], baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1 }, - age: { - base: mempoolAgeColors.map(hexToColor), - audit: [], - marginal: [], - baseLevel: (tx: TxView, rate: number, relativeTime: number) => (!tx.time ? 0 : Math.max(0, Math.round(1.25 * Math.log2((Math.max(1, relativeTime - tx.time)))))) - }, } for (const key in defaultColors) { const base = defaultColors[key].base; @@ -131,4 +125,21 @@ export function defaultColorFunction( return levelColor; } } +} + +export function ageColorFunction( + tx: TxView, + colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee, + auditColors: { [status: string]: Color } = defaultAuditColors, + relativeTime?: number, +): Color { + const color = defaultColorFunction(tx, colors, auditColors, relativeTime); + + const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60))))))); + return { + r: color.r, + g: color.g, + b: color.b, + a: color.a * (1 - ageLevel) + }; } \ No newline at end of file From fc5312549d2ec499a78b0e78b8f1cbc05fe5ea4e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 4 Apr 2024 07:42:31 +0000 Subject: [PATCH 09/44] Switch gradient toggle fee -> default --- .../app/components/block-filters/block-filters.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/block-filters/block-filters.component.html b/frontend/src/app/components/block-filters/block-filters.component.html index 093ddc11a..83988d5cc 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -30,7 +30,7 @@
Gradient