mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 17:15:03 -07:00
Add SV2 DATUM payout hardening
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AcceptedShareProtocolMetadata1781130600000 implements MigrationInterface {
|
||||
public name = 'AcceptedShareProtocolMetadata1781130600000';
|
||||
public transaction = false;
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await this.decompressAcceptedShareChunks(queryRunner);
|
||||
await queryRunner.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'accepted_share_entity'
|
||||
AND column_name = 'protocol'
|
||||
AND COALESCE(character_maximum_length, 0) < 16
|
||||
) THEN
|
||||
ALTER TABLE "accepted_share_entity" ALTER COLUMN "protocol" TYPE varchar(16);
|
||||
END IF;
|
||||
END
|
||||
$$
|
||||
`);
|
||||
await queryRunner.query(`ALTER TABLE "accepted_share_entity" ADD COLUMN IF NOT EXISTS "workSource" varchar(16) NOT NULL DEFAULT 'pool_template'`);
|
||||
await queryRunner.query(`ALTER TABLE "accepted_share_entity" ADD COLUMN IF NOT EXISTS "workProtocol" varchar(16) NOT NULL DEFAULT 'pool'`);
|
||||
await this.restoreAcceptedShareCompression(queryRunner);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_protocol_work"
|
||||
ON "accepted_share_entity" ("protocol", "workSource", "workProtocol", "acceptedAt" DESC)
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await this.decompressAcceptedShareChunks(queryRunner);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_protocol_work"`);
|
||||
await queryRunner.query(`ALTER TABLE "accepted_share_entity" DROP COLUMN IF EXISTS "workProtocol"`);
|
||||
await queryRunner.query(`ALTER TABLE "accepted_share_entity" DROP COLUMN IF EXISTS "workSource"`);
|
||||
await queryRunner.query(`ALTER TABLE "accepted_share_entity" ALTER COLUMN "protocol" TYPE varchar(8)`);
|
||||
await this.restoreAcceptedShareCompression(queryRunner);
|
||||
}
|
||||
|
||||
private async decompressAcceptedShareChunks(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`SELECT remove_compression_policy('accepted_share_entity', if_exists => TRUE)`);
|
||||
await queryRunner.query(`
|
||||
DO $$
|
||||
DECLARE
|
||||
chunk_name regclass;
|
||||
BEGIN
|
||||
FOR chunk_name IN SELECT show_chunks('accepted_share_entity')
|
||||
LOOP
|
||||
PERFORM decompress_chunk(chunk_name, if_compressed => TRUE);
|
||||
END LOOP;
|
||||
END
|
||||
$$
|
||||
`);
|
||||
}
|
||||
|
||||
private async restoreAcceptedShareCompression(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "accepted_share_entity" SET (
|
||||
timescaledb.compress,
|
||||
timescaledb.compress_orderby = '"acceptedAt" DESC',
|
||||
timescaledb.compress_segmentby = '"address","clientName"'
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
SELECT add_compression_policy(
|
||||
'accepted_share_entity',
|
||||
INTERVAL '24 hours',
|
||||
if_not_exists => TRUE
|
||||
)
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class BlocksSubmissionMetadata1781220000000 implements MigrationInterface {
|
||||
public name = 'BlocksSubmissionMetadata1781220000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "blocks_entity" ADD COLUMN IF NOT EXISTS "blockHash" varchar`);
|
||||
await queryRunner.query(`ALTER TABLE "blocks_entity" ADD COLUMN IF NOT EXISTS "blockSubmissionResult" varchar`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_blocks_successful_found"
|
||||
ON "blocks_entity" ("height" DESC, "createdAt" DESC)
|
||||
WHERE "blockSubmissionResult" IS NULL OR "blockSubmissionResult" = 'SUCCESS!'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_blocks_block_hash"
|
||||
ON "blocks_entity" ("blockHash")
|
||||
WHERE "blockHash" IS NOT NULL
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "UQ_blocks_block_hash"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_blocks_successful_found"`);
|
||||
await queryRunner.query(`ALTER TABLE "blocks_entity" DROP COLUMN IF EXISTS "blockSubmissionResult"`);
|
||||
await queryRunner.query(`ALTER TABLE "blocks_entity" DROP COLUMN IF EXISTS "blockHash"`);
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,8 @@ export class InitialTimescaleSchema1780859300000 implements MigrationInterface {
|
||||
"worker" varchar NOT NULL,
|
||||
"sessionId" varchar(8) NOT NULL,
|
||||
"blockData" varchar NOT NULL,
|
||||
"blockHash" varchar,
|
||||
"blockSubmissionResult" varchar,
|
||||
"deletedAt" timestamptz,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class PayoutSnapshots1780969800000 implements MigrationInterface {
|
||||
public name = 'PayoutSnapshots1780969800000';
|
||||
public transaction = false;
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS "payout_snapshot" (
|
||||
"id" bigserial PRIMARY KEY,
|
||||
"method" varchar(32) NOT NULL,
|
||||
"status" varchar(16) NOT NULL DEFAULT 'finalized',
|
||||
"blockHeight" bigint NOT NULL,
|
||||
"coinbaseValueSats" bigint NOT NULL,
|
||||
"networkDifficulty" numeric NOT NULL,
|
||||
"windowTargetDifficulty" numeric NOT NULL DEFAULT 0,
|
||||
"windowFactor" numeric NOT NULL DEFAULT 4,
|
||||
"feeAddress" varchar(62),
|
||||
"feeSats" bigint NOT NULL DEFAULT 0,
|
||||
"minPayoutSats" integer NOT NULL DEFAULT 546,
|
||||
"coinbaseWeightBudget" integer NOT NULL DEFAULT 50000,
|
||||
"startBatchId" bigint NOT NULL REFERENCES "share_rollup_batch" ("id"),
|
||||
"endBatchId" bigint NOT NULL REFERENCES "share_rollup_batch" ("id"),
|
||||
"windowStartShareIndex" bigint NOT NULL,
|
||||
"windowEndShareIndex" bigint NOT NULL,
|
||||
"totalCreditedDifficulty" numeric NOT NULL,
|
||||
"totalAcceptedShareCount" bigint NOT NULL,
|
||||
"eligibleAddressCount" integer NOT NULL,
|
||||
"includedOutputCount" integer NOT NULL,
|
||||
"distributedSats" bigint NOT NULL,
|
||||
"unallocatedRemainderSats" bigint NOT NULL,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payout_snapshot_latest"
|
||||
ON "payout_snapshot" ("status", "createdAt" DESC, "id" DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payout_snapshot_window"
|
||||
ON "payout_snapshot" ("windowEndShareIndex" DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IDX_payout_snapshot_template_window"
|
||||
ON "payout_snapshot" ("method", "blockHeight", "coinbaseValueSats", "windowEndShareIndex")
|
||||
WHERE "status" = 'finalized'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS "payout_snapshot_entry" (
|
||||
"id" bigserial PRIMARY KEY,
|
||||
"snapshotId" bigint NOT NULL REFERENCES "payout_snapshot" ("id") ON DELETE CASCADE,
|
||||
"address" varchar(62) NOT NULL,
|
||||
"creditedDifficulty" numeric NOT NULL,
|
||||
"acceptedShareCount" bigint NOT NULL,
|
||||
"payoutWeight" numeric NOT NULL,
|
||||
"grossPayoutSats" bigint NOT NULL,
|
||||
"payoutSats" bigint NOT NULL,
|
||||
"balanceBeforeSats" bigint NOT NULL DEFAULT 0,
|
||||
"balanceAfterSats" bigint NOT NULL DEFAULT 0,
|
||||
"includedInCoinbase" boolean NOT NULL DEFAULT true,
|
||||
"rowType" varchar(16) NOT NULL DEFAULT 'coinbase',
|
||||
"rank" integer NOT NULL,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IDX_payout_snapshot_entry_snapshot_address"
|
||||
ON "payout_snapshot_entry" ("snapshotId", "address")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payout_snapshot_entry_snapshot_rank"
|
||||
ON "payout_snapshot_entry" ("snapshotId", "rank")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payout_snapshot_entry_address"
|
||||
ON "payout_snapshot_entry" ("address")
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS "payout_balance" (
|
||||
"address" varchar(62) PRIMARY KEY,
|
||||
"balanceSats" bigint NOT NULL DEFAULT 0,
|
||||
"totalPaidSats" bigint NOT NULL DEFAULT 0,
|
||||
"lastAcceptedShareAt" timestamptz,
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payout_balance_nonzero"
|
||||
ON "payout_balance" ("balanceSats")
|
||||
WHERE "balanceSats" != 0
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS "payout_history" (
|
||||
"id" bigserial PRIMARY KEY,
|
||||
"blockHeight" bigint NOT NULL,
|
||||
"payoutSnapshotId" bigint NOT NULL REFERENCES "payout_snapshot" ("id") ON DELETE CASCADE,
|
||||
"address" varchar(62) NOT NULL,
|
||||
"paidSats" bigint NOT NULL DEFAULT 0,
|
||||
"percent" numeric NOT NULL DEFAULT 0,
|
||||
"balanceBeforeSats" bigint NOT NULL DEFAULT 0,
|
||||
"balanceAfterSats" bigint NOT NULL DEFAULT 0,
|
||||
"rowType" varchar(16) NOT NULL DEFAULT 'coinbase',
|
||||
"createdAt" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_payout_history_block_address"
|
||||
ON "payout_history" ("blockHeight", "address")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payout_history_address_created"
|
||||
ON "payout_history" ("address", "createdAt" DESC)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "blocks_entity" ADD COLUMN IF NOT EXISTS "payoutSnapshotId" bigint`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_blocks_payout_snapshot"
|
||||
ON "blocks_entity" ("payoutSnapshotId")
|
||||
WHERE "payoutSnapshotId" IS NOT NULL
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_blocks_payout_snapshot"`);
|
||||
await queryRunner.query(`ALTER TABLE "blocks_entity" DROP COLUMN IF EXISTS "payoutSnapshotId"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_history_address_created"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "UQ_payout_history_block_address"`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "payout_history"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_balance_nonzero"`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "payout_balance"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_entry_address"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_entry_snapshot_rank"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_entry_snapshot_address"`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "payout_snapshot_entry"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_template_window"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_window"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_latest"`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "payout_snapshot"`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user