Add SV2 DATUM payout hardening

This commit is contained in:
Ben
2026-06-12 23:54:50 -04:00
parent bcd6bb0aa7
commit a33c530268
59 changed files with 7852 additions and 81 deletions
@@ -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"`);
}
}