mirror of
https://github.com/mempool/mempool.git
synced 2025-03-12 10:31:03 +01:00
Merge branch 'master' into natsoni/block-first-seen-audit
This commit is contained in:
commit
198d79f149
27 changed files with 445 additions and 88 deletions
|
@ -1,4 +1,4 @@
|
|||
import { IBitcoinApi, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||
import { IBitcoinApi, SubmitPackageResult, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
|
||||
export interface AbstractBitcoinApi {
|
||||
|
@ -23,6 +23,7 @@ export interface AbstractBitcoinApi {
|
|||
$getScriptHashTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
|
||||
$sendRawTransaction(rawTransaction: string): Promise<string>;
|
||||
$testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]>;
|
||||
$submitPackage(rawTransactions: string[], maxfeerate?: number, maxburnamount?: number): Promise<SubmitPackageResult>;
|
||||
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend>;
|
||||
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]>;
|
||||
$getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
||||
|
|
|
@ -218,3 +218,21 @@ export interface TestMempoolAcceptResult {
|
|||
},
|
||||
['reject-reason']?: string,
|
||||
}
|
||||
|
||||
export interface SubmitPackageResult {
|
||||
package_msg: string;
|
||||
"tx-results": { [wtxid: string]: TxResult };
|
||||
"replaced-transactions"?: string[];
|
||||
}
|
||||
|
||||
export interface TxResult {
|
||||
txid: string;
|
||||
"other-wtxid"?: string;
|
||||
vsize?: number;
|
||||
fees?: {
|
||||
base: number;
|
||||
"effective-feerate"?: number;
|
||||
"effective-includes"?: string[];
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||
import { IBitcoinApi, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||
import { IBitcoinApi, SubmitPackageResult, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import blocks from '../blocks';
|
||||
import mempool from '../mempool';
|
||||
|
@ -196,6 +196,10 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||
}
|
||||
}
|
||||
|
||||
$submitPackage(rawTransactions: string[], maxfeerate?: number, maxburnamount?: number): Promise<SubmitPackageResult> {
|
||||
return this.bitcoindClient.submitPackage(rawTransactions, maxfeerate ?? undefined, maxburnamount ?? undefined);
|
||||
}
|
||||
|
||||
async $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
||||
const txOut = await this.bitcoindClient.getTxOut(txId, vout, false);
|
||||
return {
|
||||
|
|
|
@ -48,6 +48,8 @@ class BitcoinRoutes {
|
|||
.post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
||||
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/package', this.$submitPackage)
|
||||
;
|
||||
|
||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||
|
@ -794,6 +796,19 @@ class BitcoinRoutes {
|
|||
}
|
||||
}
|
||||
|
||||
private async $submitPackage(req: Request, res: Response) {
|
||||
try {
|
||||
const rawTxs = Common.getTransactionsFromRequest(req);
|
||||
const maxfeerate = parseFloat(req.query.maxfeerate as string);
|
||||
const maxburnamount = parseFloat(req.query.maxburnamount as string);
|
||||
const result = await bitcoinClient.submitPackage(rawTxs, maxfeerate ?? undefined, maxburnamount ?? undefined);
|
||||
res.send(result);
|
||||
} catch (e: any) {
|
||||
handleError(req, res, 400, e.message && e.code ? 'submitpackage RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||
: (e.message || 'Error'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new BitcoinRoutes();
|
||||
|
|
|
@ -5,7 +5,7 @@ import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-fact
|
|||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import logger from '../../logger';
|
||||
import { Common } from '../common';
|
||||
import { TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||
import { SubmitPackageResult, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||
|
||||
interface FailoverHost {
|
||||
host: string,
|
||||
|
@ -332,6 +332,10 @@ class ElectrsApi implements AbstractBitcoinApi {
|
|||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
$submitPackage(rawTransactions: string[]): Promise<SubmitPackageResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
||||
return this.failoverRouter.$get<IEsploraApi.Outspend>('/tx/' + txId + '/outspend/' + vout);
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ module.exports = {
|
|||
signRawTransaction: 'signrawtransaction', // bitcoind v0.7.0+
|
||||
stop: 'stop',
|
||||
submitBlock: 'submitblock', // bitcoind v0.7.0+
|
||||
submitPackage: 'submitpackage',
|
||||
validateAddress: 'validateaddress',
|
||||
verifyChain: 'verifychain', // bitcoind v0.9.0+
|
||||
verifyMessage: 'verifymessage',
|
||||
|
|
|
@ -525,7 +525,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
tokenResult.token,
|
||||
cardTag,
|
||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||
this.accelerationUUID
|
||||
this.accelerationUUID,
|
||||
costUSD
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.processing = false;
|
||||
|
@ -624,7 +625,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
tokenResult.token,
|
||||
cardTag,
|
||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||
this.accelerationUUID
|
||||
this.accelerationUUID,
|
||||
costUSD
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.processing = false;
|
||||
|
@ -714,7 +716,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
tokenResult.token,
|
||||
tokenResult.details.cashAppPay.cashtag,
|
||||
tokenResult.details.cashAppPay.referenceId,
|
||||
this.accelerationUUID
|
||||
this.accelerationUUID,
|
||||
costUSD
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.processing = false;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="interval">
|
||||
<div class="interval-time">
|
||||
@if (eta) {
|
||||
~<app-time [time]="eta?.wait / 1000"></app-time> <!-- <span *ngIf="accelerateRatio > 1" class="compare"> ({{ accelerateRatio }}x faster)</span> -->
|
||||
~<app-time [time]="eta?.wait / 1000"></app-time>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,8 +48,6 @@
|
|||
<div class="interval-time">
|
||||
<app-time [time]="acceleratedToMined"></app-time>
|
||||
</div>
|
||||
} @else if (standardETA && !tx.status.confirmed) {
|
||||
<!-- ~<app-time [time]="standardETA / 1000 - now"></app-time> -->
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,19 +11,14 @@ import { MiningService } from '../../services/mining.service';
|
|||
})
|
||||
export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||
@Input() transactionTime: number;
|
||||
@Input() acceleratedAt: number;
|
||||
@Input() tx: Transaction;
|
||||
@Input() accelerationInfo: Acceleration;
|
||||
@Input() eta: ETA;
|
||||
// A mined transaction has standard ETA and accelerated ETA undefined
|
||||
// A transaction in mempool has either standardETA defined (if accelerated) or acceleratedETA defined (if not accelerated yet)
|
||||
@Input() standardETA: number;
|
||||
@Input() acceleratedETA: number;
|
||||
|
||||
acceleratedAt: number;
|
||||
now: number;
|
||||
accelerateRatio: number;
|
||||
useAbsoluteTime: boolean = false;
|
||||
interval: number;
|
||||
firstSeenToAccelerated: number;
|
||||
acceleratedToMined: number;
|
||||
|
||||
|
@ -36,30 +31,17 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
|||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000;
|
||||
this.updateTimes();
|
||||
|
||||
this.miningService.getPools().subscribe(pools => {
|
||||
for (const pool of pools) {
|
||||
this.poolsData[pool.unique_id] = pool;
|
||||
}
|
||||
});
|
||||
|
||||
this.updateTimes();
|
||||
this.interval = window.setInterval(this.updateTimes.bind(this), 60000);
|
||||
}
|
||||
|
||||
ngOnChanges(changes): void {
|
||||
// Hide standard ETA while we don't have a proper standard ETA calculation, see https://github.com/mempool/mempool/issues/65
|
||||
|
||||
// if (changes?.eta?.currentValue || changes?.standardETA?.currentValue || changes?.acceleratedETA?.currentValue) {
|
||||
// if (changes?.eta?.currentValue) {
|
||||
// if (changes?.acceleratedETA?.currentValue) {
|
||||
// this.accelerateRatio = Math.floor((Math.floor(changes.eta.currentValue.time / 1000) - this.now) / (Math.floor(changes.acceleratedETA.currentValue / 1000) - this.now));
|
||||
// } else if (changes?.standardETA?.currentValue) {
|
||||
// this.accelerateRatio = Math.floor((Math.floor(changes.standardETA.currentValue / 1000) - this.now) / (Math.floor(changes.eta.currentValue.time / 1000) - this.now));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
this.updateTimes();
|
||||
}
|
||||
|
||||
updateTimes(): void {
|
||||
|
@ -69,10 +51,6 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
|||
this.acceleratedToMined = Math.max(0, this.tx.status.block_time - this.acceleratedAt);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
onHover(event, status: string): void {
|
||||
if (status === 'seen') {
|
||||
this.hoverInfo = {
|
||||
|
|
|
@ -27,6 +27,14 @@
|
|||
<app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login>
|
||||
</div>
|
||||
}
|
||||
@else if (user && user.status === 'pending' && !user.email && user.snsId) {
|
||||
<div class="alert alert-danger w-100 col d-flex justify-content-center text-left">
|
||||
<span class="d-flex">
|
||||
<fa-icon [icon]="['fas', 'exclamation-triangle']" [fixedWidth]="true" class="mr-1"></fa-icon>
|
||||
<span>Please <a class="text-primary" [routerLink]="['/services/account/settings']">verify your account</a> by providing a valid email address. To mitigate spam, we delete unverified accounts at regular intervals.</span>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@else if (error === 'not_available') {
|
||||
<!-- User logged in but not a paid user or did not link its Twitter account -->
|
||||
<div class="alert alert-mempool d-block text-center w-100">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Component, OnDestroy, OnInit, ChangeDetectorRef } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators, ValidatorFn, AbstractControl, ValidationErrors } from "@angular/forms";
|
||||
import { Subscription } from "rxjs";
|
||||
import { StorageService } from "../../services/storage.service";
|
||||
import { ServicesApiServices } from "../../services/services-api.service";
|
||||
import { getRegex } from "../../shared/regex.utils";
|
||||
import { StateService } from "../../services/state.service";
|
||||
|
@ -34,7 +33,6 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
|||
|
||||
constructor(
|
||||
private cd: ChangeDetectorRef,
|
||||
private storageService: StorageService,
|
||||
private servicesApiService: ServicesApiServices,
|
||||
private formBuilder: FormBuilder,
|
||||
private stateService: StateService,
|
||||
|
@ -56,14 +54,17 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.user = this.storageService.getAuth()?.user ?? null;
|
||||
if (!this.user) {
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup form
|
||||
this.updateFaucetStatus();
|
||||
this.servicesApiService.userSubject$.subscribe(user => {
|
||||
this.user = user;
|
||||
if (!user) {
|
||||
this.loading = false;
|
||||
this.cd.markForCheck();
|
||||
return;
|
||||
}
|
||||
// Setup form
|
||||
this.updateFaucetStatus();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
// Track transaction
|
||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||
|
@ -145,9 +146,6 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
|||
'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4')), this.getNotFaucetAddressValidator(faucetAddress)]],
|
||||
'satoshis': [min, [Validators.required, Validators.min(min), Validators.max(max)]]
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
updateForm(min, max, faucetAddress: string): void {
|
||||
|
@ -160,6 +158,8 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
|||
this.faucetForm.get('satoshis').updateValueAndValidity();
|
||||
this.faucetForm.get('satoshis').markAsDirty();
|
||||
}
|
||||
this.loading = false;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
setAmount(value: number): void {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
@if (runestone?.etching.premine > 0) {
|
||||
<ng-container i18n="ord.premine-n-runes">
|
||||
<span>Premine</span>
|
||||
<span class="amount"> {{ runestone.etching.premine >= 100000 ? (toNumber(runestone.etching.premine) | amountShortener:undefined:undefined:true) : runestone.etching.premine }} </span>
|
||||
<span class="amount"> {{ getAmount(runestone.etching.premine, runestone.etching.divisibility || 0) >= 100000 ? (getAmount(runestone.etching.premine, runestone.etching.divisibility || 0) | amountShortener:undefined:undefined:true) : getAmount(runestone.etching.premine, runestone.etching.divisibility || 0) }} </span>
|
||||
{{ runestone.etching.symbol }}
|
||||
<span class="name">{{ runestone.etching.spacedName }}</span>
|
||||
<span> ({{ toNumber(runestone.etching.premine) / toNumber(runestone.etching.supply) * 100 | amountShortener:0}}% of total supply)</span>
|
||||
|
|
|
@ -9,4 +9,66 @@
|
|||
<p class="red-color d-inline">{{ error }}</p> <a *ngIf="txId" [routerLink]="['/tx/' | relativeUrl, txId]">{{ txId }}</a>
|
||||
</form>
|
||||
|
||||
@if (network === '' || network === 'testnet' || network === 'testnet4' || network === 'signet') {
|
||||
<br>
|
||||
<h1 class="text-left" style="margin-top: 1rem;" i18n="shared.submit-transactions|Submit Package">Submit Package</h1>
|
||||
|
||||
<form [formGroup]="submitTxsForm" (submit)="submitTxsForm.valid && submitTxs()" novalidate>
|
||||
<div class="mb-3">
|
||||
<textarea formControlName="txs" class="form-control" rows="5" i18n-placeholder="transaction.test-transactions" placeholder="Comma-separated list of raw transactions"></textarea>
|
||||
</div>
|
||||
<label i18n="test.tx.max-fee-rate">Maximum fee rate (sat/vB)</label>
|
||||
<input type="number" class="form-control input-dark" formControlName="maxfeerate" id="maxfeerate"
|
||||
[value]="10000" placeholder="10,000 s/vb" [class]="{invalid: invalidMaxfeerate}">
|
||||
<label i18n="submitpackage.tx.max-burn-amount">Maximum burn amount (sats)</label>
|
||||
<input type="number" class="form-control input-dark" formControlName="maxburnamount" id="maxburnamount"
|
||||
[value]="0" placeholder="0 sat" [class]="{invalid: invalidMaxburnamount}">
|
||||
<br>
|
||||
<button [disabled]="isLoadingPackage" type="submit" class="btn btn-primary mr-2" i18n="shared.submit-transactions|Submit Package">Submit Package</button>
|
||||
<p *ngIf="errorPackage" class="red-color d-inline">{{ errorPackage }}</p>
|
||||
<p *ngIf="packageMessage" class="d-inline">{{ packageMessage }}</p>
|
||||
|
||||
</form>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="box" *ngIf="results?.length">
|
||||
<table class="accept-results table table-fixed table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="allowed" i18n="test-tx.is-allowed">Allowed?</th>
|
||||
<th class="txid" i18n="dashboard.latest-transactions.txid">TXID</th>
|
||||
<th class="rate" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</th>
|
||||
<th class="reason" i18n="test-tx.rejection-reason">Rejection reason</th>
|
||||
</tr>
|
||||
<ng-container *ngFor="let result of results;">
|
||||
<tr>
|
||||
<td class="allowed">
|
||||
@if (result.error == null) {
|
||||
<span>✅</span>
|
||||
}
|
||||
@else {
|
||||
<span>❌</span>
|
||||
}
|
||||
</td>
|
||||
<td class="txid">
|
||||
@if (!result.error) {
|
||||
<a [routerLink]="['/tx/' | relativeUrl, result.txid]"><app-truncate [text]="result.txid"></app-truncate></a>
|
||||
} @else {
|
||||
<app-truncate [text]="result.txid"></app-truncate>
|
||||
}
|
||||
</td>
|
||||
<td class="rate">
|
||||
<app-fee-rate *ngIf="result.fees?.['effective-feerate'] != null" [fee]="result.fees?.['effective-feerate'] * 100000"></app-fee-rate>
|
||||
<span *ngIf="result.fees?.['effective-feerate'] == null">-</span>
|
||||
</td>
|
||||
<td class="reason">
|
||||
{{ result.error || '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
|
@ -7,6 +7,7 @@ import { OpenGraphService } from '../../services/opengraph.service';
|
|||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { TxResult } from '../../interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-push-transaction',
|
||||
|
@ -19,6 +20,16 @@ export class PushTransactionComponent implements OnInit {
|
|||
txId: string = '';
|
||||
isLoading = false;
|
||||
|
||||
submitTxsForm: UntypedFormGroup;
|
||||
errorPackage: string = '';
|
||||
packageMessage: string = '';
|
||||
results: TxResult[] = [];
|
||||
invalidMaxfeerate = false;
|
||||
invalidMaxburnamount = false;
|
||||
isLoadingPackage = false;
|
||||
|
||||
network = this.stateService.network;
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private apiService: ApiService,
|
||||
|
@ -35,6 +46,14 @@ export class PushTransactionComponent implements OnInit {
|
|||
txHash: ['', Validators.required],
|
||||
});
|
||||
|
||||
this.submitTxsForm = this.formBuilder.group({
|
||||
txs: ['', Validators.required],
|
||||
maxfeerate: ['', Validators.min(0)],
|
||||
maxburnamount: ['', Validators.min(0)],
|
||||
});
|
||||
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
|
||||
this.seoService.setTitle($localize`:@@meta.title.push-tx:Broadcast Transaction`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.push-tx:Broadcast a transaction to the ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} network using the transaction's hash.`);
|
||||
this.ogService.setManualOgImage('tx-push.jpg');
|
||||
|
@ -59,7 +78,7 @@ export class PushTransactionComponent implements OnInit {
|
|||
},
|
||||
(error) => {
|
||||
if (typeof error.error === 'string') {
|
||||
const matchText = error.error.match('"message":"(.*?)"');
|
||||
const matchText = error.error.replace(/\\/g, '').match('"message":"(.*?)"');
|
||||
this.error = 'Failed to broadcast transaction, reason: ' + (matchText && matchText[1] || error.error);
|
||||
} else if (error.message) {
|
||||
this.error = 'Failed to broadcast transaction, reason: ' + error.message;
|
||||
|
@ -70,6 +89,67 @@ export class PushTransactionComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
submitTxs() {
|
||||
let txs: string[] = [];
|
||||
try {
|
||||
txs = (this.submitTxsForm.get('txs')?.value as string).split(',').map(hex => hex.trim());
|
||||
if (txs?.length === 1) {
|
||||
this.pushTxForm.get('txHash').setValue(txs[0]);
|
||||
this.submitTxsForm.get('txs').setValue('');
|
||||
this.postTx();
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
this.errorPackage = e?.message;
|
||||
return;
|
||||
}
|
||||
|
||||
let maxfeerate;
|
||||
let maxburnamount;
|
||||
this.invalidMaxfeerate = false;
|
||||
this.invalidMaxburnamount = false;
|
||||
try {
|
||||
const maxfeerateVal = this.submitTxsForm.get('maxfeerate')?.value;
|
||||
if (maxfeerateVal != null && maxfeerateVal !== '') {
|
||||
maxfeerate = parseFloat(maxfeerateVal) / 100_000;
|
||||
}
|
||||
} catch (e) {
|
||||
this.invalidMaxfeerate = true;
|
||||
}
|
||||
try {
|
||||
const maxburnamountVal = this.submitTxsForm.get('maxburnamount')?.value;
|
||||
if (maxburnamountVal != null && maxburnamountVal !== '') {
|
||||
maxburnamount = parseInt(maxburnamountVal) / 100_000_000;
|
||||
}
|
||||
} catch (e) {
|
||||
this.invalidMaxburnamount = true;
|
||||
}
|
||||
|
||||
this.isLoadingPackage = true;
|
||||
this.errorPackage = '';
|
||||
this.results = [];
|
||||
this.apiService.submitPackage$(txs, maxfeerate === 0.1 ? null : maxfeerate, maxburnamount === 0 ? null : maxburnamount)
|
||||
.subscribe((result) => {
|
||||
this.isLoadingPackage = false;
|
||||
|
||||
this.packageMessage = result['package_msg'];
|
||||
for (let wtxid in result['tx-results']) {
|
||||
this.results.push(result['tx-results'][wtxid]);
|
||||
}
|
||||
|
||||
this.submitTxsForm.reset();
|
||||
},
|
||||
(error) => {
|
||||
if (typeof error.error?.error === 'string') {
|
||||
const matchText = error.error.error.replace(/\\/g, '').match('"message":"(.*?)"');
|
||||
this.errorPackage = matchText && matchText[1] || error.error.error;
|
||||
} else if (error.message) {
|
||||
this.errorPackage = error.message;
|
||||
}
|
||||
this.isLoadingPackage = false;
|
||||
});
|
||||
}
|
||||
|
||||
private async handleColdcardPushTx(fragmentParams: URLSearchParams): Promise<boolean> {
|
||||
// maybe conforms to Coldcard nfc-pushtx spec
|
||||
if (fragmentParams && fragmentParams.get('t')) {
|
||||
|
|
|
@ -74,7 +74,7 @@ export class TestTransactionsComponent implements OnInit {
|
|||
},
|
||||
(error) => {
|
||||
if (typeof error.error === 'string') {
|
||||
const matchText = error.error.match('"message":"(.*?)"');
|
||||
const matchText = error.error.replace(/\\/g, '').match('"message":"(.*?)"');
|
||||
this.error = matchText && matchText[1] || error.error;
|
||||
} else if (error.message) {
|
||||
this.error = error.message;
|
||||
|
|
|
@ -164,12 +164,12 @@
|
|||
<br>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="transactionTime && isAcceleration">
|
||||
<ng-container *ngIf="transactionTime > 0 && tx.acceleratedAt > 0 && isAcceleration">
|
||||
<div class="title float-left">
|
||||
<h2 id="acceleration-timeline" i18n="transaction.acceleration-timeline|Acceleration Timeline">Acceleration Timeline</h2>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<app-acceleration-timeline [transactionTime]="transactionTime" [tx]="tx" [accelerationInfo]="accelerationInfo" [eta]="(ETA$ | async)" [standardETA]="(standardETA$ | async)?.time"></app-acceleration-timeline>
|
||||
<app-acceleration-timeline [transactionTime]="transactionTime" [acceleratedAt]="tx.acceleratedAt" [tx]="tx" [accelerationInfo]="accelerationInfo" [eta]="(ETA$ | async)"></app-acceleration-timeline>
|
||||
<br>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -119,7 +119,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
txChanged$ = new BehaviorSubject<boolean>(false); // triggered whenever this.tx changes (long term, we should refactor to make this.tx an observable itself)
|
||||
isAccelerated$ = new BehaviorSubject<boolean>(false); // refactor this to make isAccelerated an observable itself
|
||||
ETA$: Observable<ETA | null>;
|
||||
standardETA$: Observable<ETA | null>;
|
||||
isCached: boolean = false;
|
||||
now = Date.now();
|
||||
da$: Observable<DifficultyAdjustment>;
|
||||
|
@ -883,21 +882,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.miningStats = stats;
|
||||
this.isAccelerated$.next(this.isAcceleration); // hack to trigger recalculation of ETA without adding another source observable
|
||||
});
|
||||
if (!this.tx.status?.confirmed) {
|
||||
this.standardETA$ = combineLatest([
|
||||
this.stateService.mempoolBlocks$.pipe(startWith(null)),
|
||||
this.stateService.difficultyAdjustment$.pipe(startWith(null)),
|
||||
]).pipe(
|
||||
map(([mempoolBlocks, da]) => {
|
||||
return this.etaService.calculateUnacceleratedETA(
|
||||
this.tx,
|
||||
mempoolBlocks,
|
||||
da,
|
||||
this.cpfpInfo,
|
||||
);
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
this.isAccelerated$.next(this.isAcceleration);
|
||||
}
|
||||
|
|
|
@ -453,3 +453,21 @@ export interface TestMempoolAcceptResult {
|
|||
},
|
||||
['reject-reason']?: string,
|
||||
}
|
||||
|
||||
export interface SubmitPackageResult {
|
||||
package_msg: string;
|
||||
"tx-results": { [wtxid: string]: TxResult };
|
||||
"replaced-transactions"?: string[];
|
||||
}
|
||||
|
||||
export interface TxResult {
|
||||
txid: string;
|
||||
"other-wtxid"?: string;
|
||||
vsize?: number;
|
||||
fees?: {
|
||||
base: number;
|
||||
"effective-feerate"?: number;
|
||||
"effective-includes"?: string[];
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights,
|
||||
RbfTree, BlockAudit, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo, TestMempoolAcceptResult } from '../interfaces/node-api.interface';
|
||||
RbfTree, BlockAudit, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo, TestMempoolAcceptResult,
|
||||
SubmitPackageResult} from '../interfaces/node-api.interface';
|
||||
import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, take, tap } from 'rxjs';
|
||||
import { StateService } from './state.service';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
|
@ -244,6 +245,19 @@ export class ApiService {
|
|||
return this.httpClient.post<TestMempoolAcceptResult[]>(this.apiBaseUrl + this.apiBasePath + `/api/txs/test${maxfeerate != null ? '?maxfeerate=' + maxfeerate.toFixed(8) : ''}`, rawTxs);
|
||||
}
|
||||
|
||||
submitPackage$(rawTxs: string[], maxfeerate?: number, maxburnamount?: number): Observable<SubmitPackageResult> {
|
||||
const queryParams = [];
|
||||
|
||||
if (maxfeerate) {
|
||||
queryParams.push(`maxfeerate=${maxfeerate}`);
|
||||
}
|
||||
|
||||
if (maxburnamount) {
|
||||
queryParams.push(`maxburnamount=${maxburnamount}`);
|
||||
}
|
||||
return this.httpClient.post<SubmitPackageResult>(this.apiBaseUrl + this.apiBasePath + '/api/v1/txs/package' + (queryParams.length > 0 ? `?${queryParams.join('&')}` : ''), rawTxs);
|
||||
}
|
||||
|
||||
getTransactionStatus$(txid: string): Observable<any> {
|
||||
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx/' + txid + '/status');
|
||||
}
|
||||
|
|
|
@ -135,16 +135,16 @@ export class ServicesApiServices {
|
|||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate`, { txInput: txInput, userBid: userBid, accelerationUUID: accelerationUUID });
|
||||
}
|
||||
|
||||
accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, accelerationUUID: string) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
||||
accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD });
|
||||
}
|
||||
|
||||
accelerateWithApplePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
||||
accelerateWithApplePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD });
|
||||
}
|
||||
|
||||
accelerateWithGooglePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
||||
accelerateWithGooglePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD });
|
||||
}
|
||||
|
||||
getAccelerations$(): Observable<Acceleration[]> {
|
||||
|
|
|
@ -70,6 +70,12 @@ export class GeolocationComponent implements OnChanges {
|
|||
if (this.type === 'node') {
|
||||
const city = this.data.city ? this.data.city : '';
|
||||
|
||||
// Handle city-states like Singapore or Hong Kong
|
||||
if (city && city === this.data?.country) {
|
||||
this.formattedLocation = `${this.data.country} ${getFlagEmoji(this.data.iso)}`;
|
||||
return;
|
||||
}
|
||||
|
||||
// City
|
||||
this.formattedLocation = `${city}`;
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 HAUS HOPPE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
*/
|
||||
|
||||
// Adapted from https://github.com/ordpool-space/ordpool-parser/tree/ce04d7a5b6bb1cf37b9fdadd77ba430f5bd6e7d6/src
|
||||
// Utils functions to decode ord inscriptions
|
||||
|
||||
|
|
|
@ -313,20 +313,24 @@ export function getRegex(type: RegexType, network?: Network): RegExp {
|
|||
}
|
||||
regex += `)`; // End the non-capturing group
|
||||
break;
|
||||
// Match a date in the format YYYY-MM-DD (optional: HH:MM)
|
||||
// Match a date in the format YYYY-MM-DD (optional: HH:MM or HH:MM:SS)
|
||||
// [Testing Order]: any order is fine
|
||||
case `date`:
|
||||
regex += `(?:`; // Start a non-capturing group
|
||||
regex += `${NUMBER_CHARS}{4}`; // Exactly 4 digits
|
||||
regex += `[-/]`; // 1 instance of the symbol "-" or "/"
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||
regex += `[-/]`; // 1 instance of the symbol "-" or "/"
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||
regex += `(?:`; // Start a non-capturing group
|
||||
regex += ` `; // 1 instance of the symbol " "
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||
regex += `:`; // 1 instance of the symbol ":"
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||
regex += `(?:`; // Start a non-capturing group for optional seconds
|
||||
regex += `:`; // 1 instance of the symbol ":"
|
||||
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||
regex += `)?`; // End the non-capturing group
|
||||
regex += `)?`; // End the non-capturing group. This group appears 0 or 1 times
|
||||
regex += `)`; // End the non-capturing group
|
||||
break;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@reboot sleep 5 ; /usr/local/bin/bitcoind -testnet >/dev/null 2>&1
|
||||
@reboot sleep 5 ; /usr/local/bin/bitcoind -testnet4 >/dev/null 2>&1
|
||||
@reboot sleep 5 ; /usr/local/bin/bitcoind -signet >/dev/null 2>&1
|
||||
@reboot sleep 10 ; screen -dmS mainnet /bitcoin/electrs/start mainnet
|
||||
@reboot sleep 10 ; screen -dmS testnet /bitcoin/electrs/start testnet
|
||||
|
|
|
@ -47,6 +47,7 @@ UNFURL_INSTALL=ON
|
|||
BITCOIN_MAINNET_ENABLE=ON
|
||||
BITCOIN_MAINNET_MINFEE_ENABLE=ON
|
||||
BITCOIN_TESTNET_ENABLE=ON
|
||||
BITCOIN_TESTNET4_ENABLE=ON
|
||||
BITCOIN_SIGNET_ENABLE=ON
|
||||
BITCOIN_MAINNET_LIGHTNING_ENABLE=ON
|
||||
BITCOIN_TESTNET_LIGHTNING_ENABLE=ON
|
||||
|
@ -100,6 +101,13 @@ BITCOIN_TESTNET_P2P_PORT=18333
|
|||
BITCOIN_TESTNET_RPC_HOST=127.0.0.1
|
||||
BITCOIN_TESTNET_RPC_PORT=18332
|
||||
|
||||
# used for firewall configuration
|
||||
BITCOIN_TESTNET4_P2P_HOST=127.0.0.1
|
||||
BITCOIN_TESTNET4_P2P_PORT=48333
|
||||
# used for RPC communication
|
||||
BITCOIN_TESTNET4_RPC_HOST=127.0.0.1
|
||||
BITCOIN_TESTNET4_RPC_PORT=48332
|
||||
|
||||
# used for firewall configuration
|
||||
BITCOIN_SIGNET_P2P_HOST=127.0.0.1
|
||||
BITCOIN_SIGNET_P2P_PORT=18333
|
||||
|
@ -139,6 +147,11 @@ ELECTRS_LIQUID_HTTP_PORT=3001
|
|||
ELECTRS_TESTNET_HTTP_HOST=127.0.0.1
|
||||
ELECTRS_TESTNET_HTTP_PORT=3002
|
||||
|
||||
# set either socket or TCP host/port, not both
|
||||
#ELECTRS_TESTNET4_HTTP_SOCK=/tmp/bitcoin.testnet4.electrs
|
||||
ELECTRS_TESTNET4_HTTP_HOST=127.0.0.1
|
||||
ELECTRS_TESTNET4_HTTP_PORT=3005
|
||||
|
||||
# set either socket or TCP host/port, not both
|
||||
#ELECTRS_SIGNET_HTTP_SOCK=/tmp/bitcoin.testnet.electrs
|
||||
ELECTRS_SIGNET_HTTP_HOST=127.0.0.1
|
||||
|
@ -164,6 +177,11 @@ MEMPOOL_LIQUID_HTTP_PORT=8998
|
|||
MEMPOOL_TESTNET_HTTP_HOST=127.0.0.1
|
||||
MEMPOOL_TESTNET_HTTP_PORT=8997
|
||||
|
||||
# set either socket or TCP host/port, not both
|
||||
#MEMPOOL_TESTNET4_HTTP_SOCK=/tmp/bitcoin.testnet.mempool
|
||||
MEMPOOL_TESTNET4_HTTP_HOST=127.0.0.1
|
||||
MEMPOOL_TESTNET4_HTTP_PORT=8990
|
||||
|
||||
# set either socket or TCP host/port, not both
|
||||
#MEMPOOL_BISQ_HTTP_SOCK=/tmp/bitcoin.bisq.mempool
|
||||
MEMPOOL_BISQ_HTTP_HOST=127.0.0.1
|
||||
|
@ -231,6 +249,7 @@ MYSQL_GROUP=mysql
|
|||
# mempool mysql user/password
|
||||
MEMPOOL_MAINNET_USER='mempool'
|
||||
MEMPOOL_TESTNET_USER='mempool_testnet'
|
||||
MEMPOOL_TESTNET4_USER='mempool_testnet4'
|
||||
MEMPOOL_SIGNET_USER='mempool_signet'
|
||||
MEMPOOL_MAINNET_LIGHTNING_USER='mempool_mainnet_lightning'
|
||||
MEMPOOL_TESTNET_LIGHTNING_USER='mempool_testnet_lightning'
|
||||
|
@ -241,6 +260,7 @@ MEMPOOL_BISQ_USER='mempool_bisq'
|
|||
# generate random hex string
|
||||
MEMPOOL_MAINNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||
MEMPOOL_TESTNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||
MEMPOOL_TESTNET4_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||
MEMPOOL_SIGNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||
MEMPOOL_MAINNET_LIGHTNING_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||
MEMPOOL_TESTNET_LIGHTNING_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||
|
@ -265,7 +285,9 @@ BITCOIN_HOME=/bitcoin
|
|||
|
||||
# bitcoin testnet data
|
||||
BITCOIN_TESTNET_DATA=${BITCOIN_HOME}/testnet3
|
||||
# bitcoin testnet data
|
||||
# bitcoin testnet4 data
|
||||
BITCOIN_TESTNET4_DATA=${BITCOIN_HOME}/testnet4
|
||||
# bitcoin signet data
|
||||
BITCOIN_SIGNET_DATA=${BITCOIN_HOME}/signet
|
||||
|
||||
# bitcoin electrs source/binaries
|
||||
|
@ -279,6 +301,9 @@ ELECTRS_MAINNET_DATA=${ELECTRS_DATA_ROOT}/mainnet
|
|||
# bitcoin testnet electrs database, only a few GB
|
||||
ELECTRS_TESTNET_ZPOOL=${ZPOOL}
|
||||
ELECTRS_TESTNET_DATA=${ELECTRS_DATA_ROOT}/testnet
|
||||
# bitcoin testnet4 electrs database, only a few GB
|
||||
ELECTRS_TESTNET4_ZPOOL=${ZPOOL}
|
||||
ELECTRS_TESTNET4_DATA=${ELECTRS_DATA_ROOT}/testnet4
|
||||
# bitcoin signet electrs database, only a few GB
|
||||
ELECTRS_SIGNET_ZPOOL=${ZPOOL}
|
||||
ELECTRS_SIGNET_DATA=${ELECTRS_DATA_ROOT}/signet
|
||||
|
@ -332,7 +357,7 @@ BITCOIN_REPO_URL=https://github.com/bitcoin/bitcoin
|
|||
BITCOIN_REPO_NAME=bitcoin
|
||||
BITCOIN_REPO_BRANCH=master
|
||||
#BITCOIN_LATEST_RELEASE=$(curl -s https://api.github.com/repos/bitcoin/bitcoin/releases/latest|grep tag_name|head -1|cut -d '"' -f4)
|
||||
BITCOIN_LATEST_RELEASE=v25.1
|
||||
BITCOIN_LATEST_RELEASE=v28.0rc2
|
||||
echo -n '.'
|
||||
|
||||
BISQ_REPO_URL=https://github.com/bisq-network/bisq
|
||||
|
@ -567,6 +592,15 @@ zfsCreateFilesystems()
|
|||
done
|
||||
fi
|
||||
|
||||
# Bitcoin Testnet4
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
zfs create -o "mountpoint=${BITCOIN_TESTNET4_DATA}" "${ZPOOL}/bitcoin/testnet4"
|
||||
for folder in chainstate indexes blocks
|
||||
do
|
||||
zfs create -o "mountpoint=${BITCOIN_TESTNET4_DATA}/${folder}" "${ZPOOL}/bitcoin/testnet4/${folder}"
|
||||
done
|
||||
fi
|
||||
|
||||
# Bitcoin Signet
|
||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
zfs create -o "mountpoint=${BITCOIN_SIGNET_DATA}" "${ZPOOL}/bitcoin/signet"
|
||||
|
@ -594,6 +628,15 @@ zfsCreateFilesystems()
|
|||
done
|
||||
fi
|
||||
|
||||
# electrs testnet4 data
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
zfs create -o "mountpoint=${ELECTRS_TESTNET4_DATA}" "${ELECTRS_TESTNET4_ZPOOL}/electrs/testnet4"
|
||||
for folder in cache history txstore
|
||||
do
|
||||
zfs create -o "mountpoint=${ELECTRS_TESTNET4_DATA}/newindex/${folder}" "${ELECTRS_TESTNET4_ZPOOL}/electrs/testnet4/${folder}"
|
||||
done
|
||||
fi
|
||||
|
||||
# electrs signet data
|
||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
zfs create -o "mountpoint=${ELECTRS_SIGNET_DATA}" "${ELECTRS_SIGNET_ZPOOL}/electrs/signet"
|
||||
|
@ -651,6 +694,15 @@ ext4CreateDir()
|
|||
done
|
||||
fi
|
||||
|
||||
# Bitcoin Testnet4
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
mkdir -p "${BITCOIN_TESTNET4_DATA}"
|
||||
for folder in chainstate indexes blocks
|
||||
do
|
||||
mkdir -p "${BITCOIN_TESTNET4_DATA}/${folder}"
|
||||
done
|
||||
fi
|
||||
|
||||
# Bitcoin Signet
|
||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
mkdir -p "${BITCOIN_SIGNET_DATA}"
|
||||
|
@ -678,6 +730,15 @@ ext4CreateDir()
|
|||
done
|
||||
fi
|
||||
|
||||
# electrs testnet4 data
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
mkdir -p "${ELECTRS_TESTNET4_DATA}"
|
||||
for folder in cache history txstore
|
||||
do
|
||||
mkdir -p "${ELECTRS_TESTNET4_DATA}/newindex/${folder}"
|
||||
done
|
||||
fi
|
||||
|
||||
# electrs signet data
|
||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
mkdir -p "${ELECTRS_SIGNET_DATA}"
|
||||
|
@ -769,6 +830,7 @@ LN-Mainnet:Enable Bitcoin Mainnet Lightning:ON
|
|||
LN-Testnet:Enable Bitcoin Testnet Lightning:ON
|
||||
LN-Signet:Enable Bitcoin Signet Lightning:ON
|
||||
Testnet:Enable Bitcoin Testnet:ON
|
||||
Testnet4:Enable Bitcoin Testnet4:ON
|
||||
Signet:Enable Bitcoin Signet:ON
|
||||
Liquid:Enable Elements Liquid:ON
|
||||
Liquidtestnet:Enable Elements Liquidtestnet:ON
|
||||
|
@ -818,13 +880,19 @@ else
|
|||
BITCOIN_TESTNET_ENABLE=OFF
|
||||
fi
|
||||
|
||||
if grep Testnet4 $tempfile >/dev/null 2>&1;then
|
||||
BITCOIN_TESTNET4_ENABLE=ON
|
||||
else
|
||||
BITCOIN_TESTNET4_ENABLE=OFF
|
||||
fi
|
||||
|
||||
if grep Signet $tempfile >/dev/null 2>&1;then
|
||||
BITCOIN_SIGNET_ENABLE=ON
|
||||
else
|
||||
BITCOIN_SIGNET_ENABLE=OFF
|
||||
fi
|
||||
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_MAINNET_MINFEE_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_MAINNET_MINFEE_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_TESTNET4_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
BITCOIN_INSTALL=ON
|
||||
else
|
||||
BITCOIN_INSTALL=OFF
|
||||
|
@ -872,7 +940,7 @@ else
|
|||
CLN_INSTALL=OFF
|
||||
fi
|
||||
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_TESTNET4_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
BITCOIN_ELECTRS_INSTALL=ON
|
||||
else
|
||||
BITCOIN_ELECTRS_INSTALL=OFF
|
||||
|
@ -1216,6 +1284,9 @@ if [ "${BITCOIN_ELECTRS_INSTALL}" = ON ];then
|
|||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" chown -R "${BITCOIN_USER}:${BITCOIN_GROUP}" "${ELECTRS_TESTNET_DATA}"
|
||||
fi
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" chown -R "${BITCOIN_USER}:${BITCOIN_GROUP}" "${ELECTRS_TESTNET4_DATA}"
|
||||
fi
|
||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" chown -R "${BITCOIN_USER}:${BITCOIN_GROUP}" "${ELECTRS_SIGNET_DATA}"
|
||||
fi
|
||||
|
@ -1520,7 +1591,7 @@ fi
|
|||
# Bitcoin instance for Mainnet Minfee #
|
||||
#######################################
|
||||
|
||||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||
if [ "${BITCOIN_MAINNET_MINFEE_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Bitcoin Minfee service"
|
||||
case $OS in
|
||||
|
||||
|
@ -1550,6 +1621,23 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
|||
esac
|
||||
fi
|
||||
|
||||
#################################
|
||||
# Bitcoin instance for Testnet4 #
|
||||
#################################
|
||||
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Bitcoin Testnet service"
|
||||
case $OS in
|
||||
|
||||
FreeBSD)
|
||||
;;
|
||||
|
||||
Debian)
|
||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/bitcoin-testnet4.service" "${DEBIAN_SERVICE_HOME}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
###############################
|
||||
# Bitcoin instance for Signet #
|
||||
###############################
|
||||
|
@ -1616,6 +1704,14 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
|||
echo "[*] FIXME: must only crontab enabled daemons"
|
||||
fi
|
||||
|
||||
#########################################
|
||||
# Electrs instance for Bitcoin Testnet4 #
|
||||
#########################################
|
||||
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
echo "[*] FIXME: must only crontab enabled daemons"
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Electrs instance for Bitcoin Signet #
|
||||
#######################################
|
||||
|
@ -1668,11 +1764,15 @@ case $OS in
|
|||
echo "[*] Installing Electrs Testnet Cronjob"
|
||||
crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/start testnet\n"
|
||||
fi
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Electrs Testnet4 Cronjob"
|
||||
crontab_bitcoin+="@reboot sleep 110 ; screen -dmS testnet4 /bitcoin/electrs/start testnet4\n"
|
||||
fi
|
||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Electrs Signet Cronjob"
|
||||
crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/start signet\n"
|
||||
fi
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_TESTNET4_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" -
|
||||
fi
|
||||
|
||||
|
@ -1700,7 +1800,7 @@ fi
|
|||
|
||||
##### Mempool -> Bitcoin Mainnet instance
|
||||
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_TESTNET4_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
echo "[*] Creating Mempool instance for Bitcoin Mainnet"
|
||||
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/mainnet"
|
||||
|
@ -1727,6 +1827,15 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
|||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/testnet && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||
fi
|
||||
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
echo "[*] Creating Mempool instance for Bitcoin Testnet4"
|
||||
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/testnet4"
|
||||
|
||||
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bitcoin Testnet4"
|
||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/testnet4 && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||
fi
|
||||
|
||||
if [ "${BITCOIN_TESTNET_LIGHTNING_ENABLE}" = ON ];then
|
||||
echo "[*] Creating Mempool instance for Lightning Network on Bitcoin Testnet"
|
||||
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||
|
@ -1804,6 +1913,9 @@ grant all on mempool.* to '${MEMPOOL_MAINNET_USER}'@'localhost' identified by '$
|
|||
create database mempool_testnet;
|
||||
grant all on mempool_testnet.* to '${MEMPOOL_TESTNET_USER}'@'localhost' identified by '${MEMPOOL_TESTNET_PASS}';
|
||||
|
||||
create database mempool_testnet4;
|
||||
grant all on mempool_testnet4.* to '${MEMPOOL_TESTNET4_USER}'@'localhost' identified by '${MEMPOOL_TESTNET4_PASS}';
|
||||
|
||||
create database mempool_signet;
|
||||
grant all on mempool_signet.* to '${MEMPOOL_SIGNET_USER}'@'localhost' identified by '${MEMPOOL_SIGNET_PASS}';
|
||||
|
||||
|
@ -1832,6 +1944,8 @@ declare -x MEMPOOL_MAINNET_USER="${MEMPOOL_MAINNET_USER}"
|
|||
declare -x MEMPOOL_MAINNET_PASS="${MEMPOOL_MAINNET_PASS}"
|
||||
declare -x MEMPOOL_TESTNET_USER="${MEMPOOL_TESTNET_USER}"
|
||||
declare -x MEMPOOL_TESTNET_PASS="${MEMPOOL_TESTNET_PASS}"
|
||||
declare -x MEMPOOL_TESTNET4_USER="${MEMPOOL_TESTNET4_USER}"
|
||||
declare -x MEMPOOL_TESTNET4_PASS="${MEMPOOL_TESTNET4_PASS}"
|
||||
declare -x MEMPOOL_SIGNET_USER="${MEMPOOL_SIGNET_USER}"
|
||||
declare -x MEMPOOL_SIGNET_PASS="${MEMPOOL_SIGNET_PASS}"
|
||||
declare -x MEMPOOL_MAINNET_LIGHTNING_USER="${MEMPOOL_MAINNET_LIGHTNING_USER}"
|
||||
|
@ -1932,6 +2046,9 @@ EOF
|
|||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" systemctl enable bitcoin-testnet.service
|
||||
fi
|
||||
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" systemctl enable bitcoin-testnet4.service
|
||||
fi
|
||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" systemctl enable bitcoin-signet.service
|
||||
fi
|
||||
|
|
22
production/linux/bitcoin-testnet4.service
Normal file
22
production/linux/bitcoin-testnet4.service
Normal file
|
@ -0,0 +1,22 @@
|
|||
[Unit]
|
||||
Description=Bitcoind-testnet4
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/bitcoind -conf=bitcoin.conf -daemon -testnet4 -printtoconsole -pid=/bitcoin/bitcoind-testnet4.pid
|
||||
ExecStop=/usr/local/bin/bitcoin-cli -testnet4 stop
|
||||
|
||||
Type=forking
|
||||
PIDFile=/bitcoin/bitcoind-testnet4.pid
|
||||
Restart=on-failure
|
||||
|
||||
User=bitcoin
|
||||
Group=bitcoin
|
||||
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -10,6 +10,9 @@
|
|||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"MAINNET_BLOCK_AUDIT_START_HEIGHT": 773911,
|
||||
"TESTNET_BLOCK_AUDIT_START_HEIGHT": 2417829,
|
||||
"SIGNET_BLOCK_AUDIT_START_HEIGHT": 127609,
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"LIGHTNING": true,
|
||||
"ACCELERATOR": true,
|
||||
|
|
Loading…
Add table
Reference in a new issue