mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 17:15:03 -07:00
Add payout modes and production switchover schema
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AcceptedShareHighScores1781309000000 implements MigrationInterface {
|
||||
public name = 'AcceptedShareHighScores1781309000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS "accepted_share_high_score" (
|
||||
"id" bigserial PRIMARY KEY,
|
||||
"scope" varchar(16) NOT NULL,
|
||||
"payoutMode" varchar(16) NOT NULL DEFAULT 'all',
|
||||
"bucketDate" date NOT NULL DEFAULT DATE '1970-01-01',
|
||||
"submissionDifficulty" numeric NOT NULL DEFAULT 0,
|
||||
"acceptedAt" timestamptz,
|
||||
"bucket" timestamptz,
|
||||
"blockHeight" bigint,
|
||||
"address" varchar(128),
|
||||
"clientName" varchar(256),
|
||||
"protocol" varchar(16),
|
||||
"createdAt" timestamptz NOT NULL DEFAULT NOW(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_accepted_share_high_score_scope"
|
||||
ON "accepted_share_high_score" ("scope", "payoutMode", "bucketDate")
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_high_score_best"
|
||||
ON "accepted_share_high_score" ("payoutMode", "submissionDifficulty" DESC, "acceptedAt" DESC)
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "accepted_share_high_score"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class PayoutModes1781300000000 implements MigrationInterface {
|
||||
public name = 'PayoutModes1781300000000';
|
||||
public transaction = false;
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "accepted_share_entity" ADD COLUMN IF NOT EXISTS "payoutMode" varchar(16) NOT NULL DEFAULT 'solo'`);
|
||||
await queryRunner.query(`ALTER TABLE "client_entity" ADD COLUMN IF NOT EXISTS "payoutMode" varchar(16) NOT NULL DEFAULT 'solo'`);
|
||||
await queryRunner.query(`ALTER TABLE "blocks_entity" ADD COLUMN IF NOT EXISTS "payoutMode" varchar(16) NOT NULL DEFAULT 'solo'`);
|
||||
await queryRunner.query(`ALTER TABLE "share_rollup_batch" ADD COLUMN IF NOT EXISTS "payoutMode" varchar(16) NOT NULL DEFAULT 'pplns'`);
|
||||
await queryRunner.query(`ALTER TABLE "share_rollup_batch_summary" ADD COLUMN IF NOT EXISTS "payoutMode" varchar(16) NOT NULL DEFAULT 'pplns'`);
|
||||
await queryRunner.query(`ALTER TABLE "payout_snapshot" ADD COLUMN IF NOT EXISTS "payoutMode" varchar(16) NOT NULL DEFAULT 'pplns'`);
|
||||
await queryRunner.query(`ALTER TABLE "payout_snapshot_entry" ADD COLUMN IF NOT EXISTS "payoutMode" varchar(16) NOT NULL DEFAULT 'pplns'`);
|
||||
await queryRunner.query(`ALTER TABLE "payout_history" ADD COLUMN IF NOT EXISTS "payoutMode" varchar(16) NOT NULL DEFAULT 'pplns'`);
|
||||
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_accounting_lookup"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_unique_submission"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_share_rollup_batch_range"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_share_rollup_batch_finalized_end"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_share_rollup_summary_address_batch"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_latest"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_template_window"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "UQ_payout_history_block_address"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_history_address_created"`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_accounting_lookup"
|
||||
ON "accepted_share_entity" ("payoutMode", "address", "clientName", "acceptedAt" DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_mode_order"
|
||||
ON "accepted_share_entity" ("payoutMode", "shareIndex")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IDX_accepted_share_unique_submission"
|
||||
ON "accepted_share_entity" ("acceptedAt", "payoutMode", "protocol", "sessionId", "jobId", "nonce", "ntime", "version", "extraNonce2")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IDX_share_rollup_batch_range"
|
||||
ON "share_rollup_batch" ("payoutMode", "startShareIndex", "endShareIndex")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_share_rollup_batch_finalized_end"
|
||||
ON "share_rollup_batch" ("payoutMode", "endShareIndex" DESC)
|
||||
WHERE "status" = 'finalized'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_share_rollup_summary_address_batch"
|
||||
ON "share_rollup_batch_summary" ("payoutMode", "address", "clientName", "blockHeight", "batchId")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payout_snapshot_latest"
|
||||
ON "payout_snapshot" ("payoutMode", "status", "createdAt" DESC, "id" DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IDX_payout_snapshot_template_window"
|
||||
ON "payout_snapshot" ("payoutMode", "method", "blockHeight", "coinbaseValueSats", "windowEndShareIndex")
|
||||
WHERE "status" = 'finalized'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_payout_history_block_address"
|
||||
ON "payout_history" ("payoutMode", "blockHeight", "address")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payout_history_address_created"
|
||||
ON "payout_history" ("payoutMode", "address", "createdAt" DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_blocks_payout_mode_height"
|
||||
ON "blocks_entity" ("payoutMode", "height" DESC, "createdAt" DESC)
|
||||
`);
|
||||
|
||||
await this.recreateContinuousAggregates(queryRunner);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "accepted_share_entity"
|
||||
SET (
|
||||
timescaledb.compress_segmentby = '"payoutMode","address","clientName"',
|
||||
timescaledb.compress_orderby = '"acceptedAt" DESC'
|
||||
)
|
||||
`).catch(() => undefined);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_blocks_payout_mode_height"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_mode_order"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_accounting_lookup"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_unique_submission"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_share_rollup_batch_range"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_share_rollup_batch_finalized_end"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_share_rollup_summary_address_batch"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_latest"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_snapshot_template_window"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "UQ_payout_history_block_address"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_payout_history_address_created"`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "payout_history" DROP COLUMN IF EXISTS "payoutMode"`);
|
||||
await queryRunner.query(`ALTER TABLE "payout_snapshot_entry" DROP COLUMN IF EXISTS "payoutMode"`);
|
||||
await queryRunner.query(`ALTER TABLE "payout_snapshot" DROP COLUMN IF EXISTS "payoutMode"`);
|
||||
await queryRunner.query(`ALTER TABLE "share_rollup_batch_summary" DROP COLUMN IF EXISTS "payoutMode"`);
|
||||
await queryRunner.query(`ALTER TABLE "share_rollup_batch" DROP COLUMN IF EXISTS "payoutMode"`);
|
||||
await queryRunner.query(`ALTER TABLE "blocks_entity" DROP COLUMN IF EXISTS "payoutMode"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_entity" DROP COLUMN IF EXISTS "payoutMode"`);
|
||||
await queryRunner.query(`ALTER TABLE "accepted_share_entity" DROP COLUMN IF EXISTS "payoutMode"`);
|
||||
}
|
||||
|
||||
private async recreateContinuousAggregates(queryRunner: QueryRunner): Promise<void> {
|
||||
await this.dropContinuousAggregate(queryRunner, 'accepted_share_block_10m');
|
||||
await this.dropContinuousAggregate(queryRunner, 'accepted_share_1d');
|
||||
await this.dropContinuousAggregate(queryRunner, 'accepted_share_1h');
|
||||
await this.dropContinuousAggregate(queryRunner, 'accepted_share_10m');
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS "accepted_share_10m"
|
||||
WITH (timescaledb.continuous) AS
|
||||
SELECT
|
||||
time_bucket(INTERVAL '10 minutes', "acceptedAt") AS "bucket",
|
||||
"payoutMode",
|
||||
"address",
|
||||
"clientName",
|
||||
"sessionId",
|
||||
"clientId",
|
||||
SUM("creditedDifficulty") AS "shares",
|
||||
COUNT(*) AS "acceptedCount",
|
||||
ROUND(((SUM("creditedDifficulty") * 4294967296) / 600)) AS "hashRate"
|
||||
FROM "accepted_share_entity"
|
||||
GROUP BY "bucket", "payoutMode", "address", "clientName", "sessionId", "clientId"
|
||||
WITH NO DATA
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS "accepted_share_1h"
|
||||
WITH (timescaledb.continuous) AS
|
||||
SELECT
|
||||
time_bucket(INTERVAL '1 hour', "acceptedAt") AS "bucket",
|
||||
"payoutMode",
|
||||
"address",
|
||||
"clientName",
|
||||
SUM("creditedDifficulty") AS "shares",
|
||||
COUNT(*) AS "acceptedCount"
|
||||
FROM "accepted_share_entity"
|
||||
GROUP BY "bucket", "payoutMode", "address", "clientName"
|
||||
WITH NO DATA
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS "accepted_share_1d"
|
||||
WITH (timescaledb.continuous) AS
|
||||
SELECT
|
||||
time_bucket(INTERVAL '1 day', "acceptedAt") AS "bucket",
|
||||
"payoutMode",
|
||||
"address",
|
||||
SUM("creditedDifficulty") AS "shares",
|
||||
COUNT(*) AS "acceptedCount"
|
||||
FROM "accepted_share_entity"
|
||||
GROUP BY "bucket", "payoutMode", "address"
|
||||
WITH NO DATA
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS "accepted_share_block_10m"
|
||||
WITH (timescaledb.continuous) AS
|
||||
SELECT
|
||||
time_bucket(INTERVAL '10 minutes', "acceptedAt") AS "bucket",
|
||||
"payoutMode",
|
||||
"blockHeight",
|
||||
SUM("creditedDifficulty") AS "shares",
|
||||
COUNT(*) AS "acceptedCount",
|
||||
MAX("networkDifficulty") AS "networkDifficulty",
|
||||
MAX("submissionDifficulty") AS "bestSubmissionDifficulty"
|
||||
FROM "accepted_share_entity"
|
||||
GROUP BY "bucket", "payoutMode", "blockHeight"
|
||||
WITH NO DATA
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
SELECT add_continuous_aggregate_policy(
|
||||
'accepted_share_10m',
|
||||
start_offset => INTERVAL '2 days',
|
||||
end_offset => INTERVAL '1 minute',
|
||||
schedule_interval => INTERVAL '1 minute',
|
||||
if_not_exists => TRUE
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
SELECT add_continuous_aggregate_policy(
|
||||
'accepted_share_1h',
|
||||
start_offset => INTERVAL '30 days',
|
||||
end_offset => INTERVAL '10 minutes',
|
||||
schedule_interval => INTERVAL '10 minutes',
|
||||
if_not_exists => TRUE
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
SELECT add_continuous_aggregate_policy(
|
||||
'accepted_share_1d',
|
||||
start_offset => INTERVAL '365 days',
|
||||
end_offset => INTERVAL '1 hour',
|
||||
schedule_interval => INTERVAL '1 hour',
|
||||
if_not_exists => TRUE
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
SELECT add_continuous_aggregate_policy(
|
||||
'accepted_share_block_10m',
|
||||
start_offset => INTERVAL '180 days',
|
||||
end_offset => INTERVAL '1 minute',
|
||||
schedule_interval => INTERVAL '1 minute',
|
||||
if_not_exists => TRUE
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_block_10m_height_bucket"
|
||||
ON "accepted_share_block_10m" ("payoutMode", "blockHeight" DESC, "bucket" DESC)
|
||||
`);
|
||||
}
|
||||
|
||||
private async dropContinuousAggregate(queryRunner: QueryRunner, viewName: string): Promise<void> {
|
||||
await queryRunner.query(`SELECT remove_continuous_aggregate_policy('${viewName}', if_exists => TRUE)`).catch(() => undefined);
|
||||
await queryRunner.query(`DROP MATERIALIZED VIEW IF EXISTS "${viewName}" CASCADE`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class ShareRollupStoragePolicy1781305000000 implements MigrationInterface {
|
||||
public name = 'ShareRollupStoragePolicy1781305000000';
|
||||
public transaction = false;
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`TRUNCATE TABLE "payout_balance", "share_rollup_batch" RESTART IDENTITY CASCADE`);
|
||||
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_share_rollup_summary_address_batch"`);
|
||||
await queryRunner.query(`ALTER TABLE "share_rollup_batch_summary" DROP CONSTRAINT IF EXISTS "share_rollup_batch_summary_pkey"`);
|
||||
|
||||
await queryRunner.query(`
|
||||
SELECT create_hypertable(
|
||||
'"share_rollup_batch_summary"',
|
||||
'lastShareAt',
|
||||
chunk_time_interval => INTERVAL '1 day',
|
||||
if_not_exists => TRUE,
|
||||
migrate_data => FALSE
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_share_rollup_summary_batch"
|
||||
ON "share_rollup_batch_summary" ("batchId", "payoutMode")
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "share_rollup_batch_summary" SET (
|
||||
timescaledb.compress,
|
||||
timescaledb.compress_orderby = '"lastShareAt" DESC',
|
||||
timescaledb.compress_segmentby = '"payoutMode","batchId"'
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`SELECT remove_compression_policy('share_rollup_batch_summary', if_exists => TRUE)`);
|
||||
await queryRunner.query(`
|
||||
SELECT add_compression_policy(
|
||||
'share_rollup_batch_summary',
|
||||
INTERVAL '24 hours',
|
||||
if_not_exists => TRUE
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_accounting_lookup"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_client_lookup"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_order"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_accepted_at"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_round_best"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_protocol_work"`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE OR REPLACE PROCEDURE prune_share_rollup_batches(job_id int, config jsonb)
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
DECLARE
|
||||
retention interval := COALESCE((config ->> 'retention')::interval, INTERVAL '30 days');
|
||||
deleted_count bigint;
|
||||
BEGIN
|
||||
DELETE FROM "share_rollup_batch"
|
||||
WHERE "status" = 'finalized'
|
||||
AND "finalizedAt" IS NOT NULL
|
||||
AND "finalizedAt" < NOW() - retention;
|
||||
|
||||
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
||||
RAISE LOG 'prune_share_rollup_batches deleted % finalized batches older than %', deleted_count, retention;
|
||||
END;
|
||||
$$
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM timescaledb_information.jobs
|
||||
WHERE proc_name = 'prune_share_rollup_batches'
|
||||
) THEN
|
||||
PERFORM add_job(
|
||||
'prune_share_rollup_batches'::regproc,
|
||||
INTERVAL '1 day',
|
||||
jsonb_build_object('retention', '30 days')
|
||||
);
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`SELECT delete_job(job_id) FROM timescaledb_information.jobs WHERE proc_name = 'prune_share_rollup_batches'`);
|
||||
await queryRunner.query(`DROP PROCEDURE IF EXISTS prune_share_rollup_batches(int, jsonb)`);
|
||||
await queryRunner.query(`SELECT remove_compression_policy('share_rollup_batch_summary', if_exists => TRUE)`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_share_rollup_summary_batch"`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_share_rollup_summary_address_batch"
|
||||
ON "share_rollup_batch_summary" ("payoutMode", "address", "clientName", "blockHeight", "batchId")
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_accounting_lookup"
|
||||
ON "accepted_share_entity" ("payoutMode", "address", "clientName", "acceptedAt" DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_client_lookup"
|
||||
ON "accepted_share_entity" ("clientId", "acceptedAt" DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_order"
|
||||
ON "accepted_share_entity" ("shareIndex" DESC)
|
||||
WHERE "shareIndex" IS NOT NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_accepted_at"
|
||||
ON "accepted_share_entity" ("acceptedAt" DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_round_best"
|
||||
ON "accepted_share_entity" ("submissionDifficulty" DESC, "acceptedAt" DESC, "blockHeight")
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_protocol_work"
|
||||
ON "accepted_share_entity" ("protocol", "workSource", "workProtocol", "acceptedAt" DESC)
|
||||
`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user