diff --git a/docs/api/stacking/get-pox-cycle-signer-stackers.example.json b/docs/api/stacking/get-pox-cycle-signer-stackers.example.json index 4fa4901f5..77840afe9 100644 --- a/docs/api/stacking/get-pox-cycle-signer-stackers.example.json +++ b/docs/api/stacking/get-pox-cycle-signer-stackers.example.json @@ -6,7 +6,8 @@ { "pox_address": "15Z2sAvjgVDpcBh4vx9g2XKU8FVHYcXNaj", "stacked_amount": "686251350000000000", - "stacker_address": "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP" + "stacker_address": "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP", + "stacker_type": "solo" } ] } diff --git a/docs/api/stacking/get-pox-cycle-signers.example.json b/docs/api/stacking/get-pox-cycle-signers.example.json index 60dd71a15..5e10d7603 100644 --- a/docs/api/stacking/get-pox-cycle-signers.example.json +++ b/docs/api/stacking/get-pox-cycle-signers.example.json @@ -9,7 +9,9 @@ "stacked_amount": "686251350000000000", "stacked_amount_percent": 50, "weight": 5, - "weight_percent": 55.55555555555556 + "weight_percent": 55.55555555555556, + "solo_stacker_count": 16, + "pooled_stacker_count": 3615 }, { "signing_key": "0x029874497a7952483aa23890e9d0898696f33864d3df90939930a1f45421fe3b09", @@ -17,7 +19,9 @@ "stacked_amount": "457500900000000000", "stacked_amount_percent": 33.333333333333336, "weight": 3, - "weight_percent": 33.33333333333333 + "weight_percent": 33.33333333333333, + "solo_stacker_count": 0, + "pooled_stacker_count": 1456 }, { "signing_key": "0x02dcde79b38787b72d8e5e0af81cffa802f0a3c8452d6b46e08859165f49a72736", @@ -25,7 +29,9 @@ "stacked_amount": "228750450000000000", "stacked_amount_percent": 16.666666666666668, "weight": 1, - "weight_percent": 11.11111111111111 + "weight_percent": 11.11111111111111, + "solo_stacker_count": 637, + "pooled_stacker_count": 0 } ] } diff --git a/docs/entities/stacking/signer.example.json b/docs/entities/stacking/signer.example.json index b4e17c414..797b55be7 100644 --- a/docs/entities/stacking/signer.example.json +++ b/docs/entities/stacking/signer.example.json @@ -4,5 +4,7 @@ "stacked_amount": "686251350000000000", "stacked_amount_percent": 50, "weight": 5, - "weight_percent": 55.55555555555556 + "weight_percent": 55.55555555555556, + "solo_stacker_count": 16, + "pooled_stacker_count": 3615 } diff --git a/docs/entities/stacking/signer.schema.json b/docs/entities/stacking/signer.schema.json index 46d0e9b46..c4c7fbaac 100644 --- a/docs/entities/stacking/signer.schema.json +++ b/docs/entities/stacking/signer.schema.json @@ -8,7 +8,9 @@ "weight", "stacked_amount", "weight_percent", - "stacked_amount_percent" + "stacked_amount_percent", + "solo_stacker_count", + "pooled_stacker_count" ], "properties": { "signing_key": { @@ -29,6 +31,14 @@ }, "stacked_amount_percent": { "type": "number" + }, + "solo_stacker_count": { + "type": "integer", + "description": "The number of solo stackers associated with this signer." + }, + "pooled_stacker_count": { + "type": "integer", + "description": "The number of pooled stackers associated with this signer." } } } diff --git a/docs/entities/stacking/stacker.example.json b/docs/entities/stacking/stacker.example.json index 867791729..6dd5f3a8e 100644 --- a/docs/entities/stacking/stacker.example.json +++ b/docs/entities/stacking/stacker.example.json @@ -1,5 +1,6 @@ { "pox_address": "15Z2sAvjgVDpcBh4vx9g2XKU8FVHYcXNaj", "stacked_amount": "686251350000000000", - "stacker_address": "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP" + "stacker_address": "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP", + "stacker_type": "solo" } diff --git a/docs/entities/stacking/stacker.schema.json b/docs/entities/stacking/stacker.schema.json index 360d80b38..02a4c19f9 100644 --- a/docs/entities/stacking/stacker.schema.json +++ b/docs/entities/stacking/stacker.schema.json @@ -5,7 +5,8 @@ "required": [ "stacker_address", "stacked_amount", - "pox_address" + "pox_address", + "stacker_type" ], "properties": { "stacker_address": { @@ -16,6 +17,10 @@ }, "pox_address": { "type": "string" + }, + "stacker_type": { + "type": "string", + "enum": ["solo", "pooled"] } } } diff --git a/docs/generated.d.ts b/docs/generated.d.ts index 93896c066..a2a0851ff 100644 --- a/docs/generated.d.ts +++ b/docs/generated.d.ts @@ -3372,6 +3372,7 @@ export interface PoxStacker { stacker_address: string; stacked_amount: string; pox_address: string; + stacker_type: "solo" | "pooled"; } /** * GET request that returns signers for a PoX cycle @@ -3401,6 +3402,14 @@ export interface PoxSigner { stacked_amount: string; weight_percent: number; stacked_amount_percent: number; + /** + * The number of solo stackers associated with this signer. + */ + solo_stacker_count: number; + /** + * The number of pooled stackers associated with this signer. + */ + pooled_stacker_count: number; } /** * GET request that returns PoX cycles diff --git a/src/api/routes/v2/helpers.ts b/src/api/routes/v2/helpers.ts index 0a312a107..97b1aa603 100644 --- a/src/api/routes/v2/helpers.ts +++ b/src/api/routes/v2/helpers.ts @@ -188,6 +188,8 @@ export function parseDbPoxSigner(signer: DbPoxCycleSigner, isMainnet: boolean): stacked_amount: signer.stacked_amount, weight_percent: signer.weight_percent, stacked_amount_percent: signer.stacked_amount_percent, + pooled_stacker_count: signer.pooled_stacker_count, + solo_stacker_count: signer.solo_stacker_count, }; return result; } @@ -197,6 +199,7 @@ export function parseDbPoxSignerStacker(stacker: DbPoxCycleSignerStacker): PoxSt stacker_address: stacker.stacker, stacked_amount: stacker.locked, pox_address: stacker.pox_addr, + stacker_type: stacker.stacker_type, }; // Special handling for pool operator stackers if ( diff --git a/src/datastore/common.ts b/src/datastore/common.ts index 9c26263a8..8072a9d35 100644 --- a/src/datastore/common.ts +++ b/src/datastore/common.ts @@ -1086,8 +1086,8 @@ export interface DbPoxCycleSigner { stacked_amount: string; weight_percent: number; stacked_amount_percent: number; - // TODO: Figure this out - // total_stackers: number; + pooled_stacker_count: number; + solo_stacker_count: number; } export interface DbPoxCycleSignerStacker { @@ -1096,6 +1096,7 @@ export interface DbPoxCycleSignerStacker { pox_addr: string; name: string; amount_ustx: string; + stacker_type: 'solo' | 'pooled'; } interface ReOrgEntities { diff --git a/src/datastore/pg-store-v2.ts b/src/datastore/pg-store-v2.ts index 01c2fe879..b3405bffd 100644 --- a/src/datastore/pg-store-v2.ts +++ b/src/datastore/pg-store-v2.ts @@ -629,12 +629,52 @@ export class PgStoreV2 extends BasePgStoreModule { if (cycleCheck.count === 0) throw new InvalidRequestError(`PoX cycle not found`, InvalidRequestErrorType.invalid_param); const results = await sql<(DbPoxCycleSigner & { total: number })[]>` - SELECT - signing_key, weight, stacked_amount, weight_percent, stacked_amount_percent, - COUNT(*) OVER()::int AS total - FROM pox_sets - WHERE canonical = TRUE AND cycle_number = ${args.cycle_number} - ORDER BY weight DESC, stacked_amount DESC, signing_key + WITH signer_keys AS ( + SELECT DISTINCT ON (stacker) stacker, signer_key + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true + AND name in ('stack-aggregation-commit-indexed', 'stack-aggregation-commit') + AND start_cycle_id = ${args.cycle_number} + AND end_cycle_id = ${args.cycle_number + 1} + ORDER BY stacker, block_height DESC, tx_index DESC, event_index DESC + ), delegated_stackers AS ( + SELECT DISTINCT ON (main.stacker) + main.stacker, + sk.signer_key + FROM pox4_events main + LEFT JOIN signer_keys sk ON main.delegator = sk.stacker + WHERE main.canonical = true + AND main.microblock_canonical = true + AND main.name IN ('delegate-stack-stx', 'delegate-stack-increase', 'delegate-stack-extend') + AND main.start_cycle_id <= ${args.cycle_number} + AND main.end_cycle_id > ${args.cycle_number} + ORDER BY main.stacker, main.block_height DESC, main.microblock_sequence DESC, main.tx_index DESC, main.event_index DESC + ), solo_stackers AS ( + SELECT DISTINCT ON (stacker) + stacker, + signer_key + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true + AND name in ('stack-stx', 'stacks-increase', 'stack-extend') + AND start_cycle_id <= ${args.cycle_number} + AND end_cycle_id > ${args.cycle_number} + ORDER BY stacker, block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC + ) + SELECT + ps.signing_key, + ps.weight, + ps.stacked_amount, + ps.weight_percent, + ps.stacked_amount_percent, + COUNT(DISTINCT ds.stacker)::int AS pooled_stacker_count, + COUNT(DISTINCT ss.stacker)::int AS solo_stacker_count, + COUNT(*) OVER()::int AS total + FROM pox_sets ps + LEFT JOIN delegated_stackers ds ON ps.signing_key = ds.signer_key + LEFT JOIN solo_stackers ss ON ps.signing_key = ss.signer_key + WHERE ps.canonical = TRUE AND ps.cycle_number = ${args.cycle_number} + GROUP BY ps.signing_key, ps.weight, ps.stacked_amount, ps.weight_percent, ps.stacked_amount_percent + ORDER BY ps.weight DESC, ps.stacked_amount DESC, ps.signing_key OFFSET ${offset} LIMIT ${limit} `; @@ -654,10 +694,52 @@ export class PgStoreV2 extends BasePgStoreModule { if (cycleCheck.count === 0) throw new InvalidRequestError(`PoX cycle not found`, InvalidRequestErrorType.invalid_param); const results = await sql` - SELECT - signing_key, weight, stacked_amount, weight_percent, stacked_amount_percent - FROM pox_sets - WHERE canonical = TRUE AND cycle_number = ${args.cycle_number} AND signing_key = ${args.signer_key} + WITH signer_keys AS ( + SELECT DISTINCT ON (stacker) stacker, signer_key + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true + AND name in ('stack-aggregation-commit-indexed', 'stack-aggregation-commit') + AND start_cycle_id = ${args.cycle_number} + AND end_cycle_id = ${args.cycle_number + 1} + ORDER BY stacker, block_height DESC, tx_index DESC, event_index DESC + ), delegated_stackers AS ( + SELECT DISTINCT ON (main.stacker) + main.stacker, + sk.signer_key + FROM pox4_events main + LEFT JOIN signer_keys sk ON main.delegator = sk.stacker + WHERE main.canonical = true + AND main.microblock_canonical = true + AND main.name IN ('delegate-stack-stx', 'delegate-stack-increase', 'delegate-stack-extend') + AND main.start_cycle_id <= ${args.cycle_number} + AND main.end_cycle_id > ${args.cycle_number} + ORDER BY main.stacker, main.block_height DESC, main.microblock_sequence DESC, main.tx_index DESC, main.event_index DESC + ), solo_stackers AS ( + SELECT DISTINCT ON (stacker) + stacker, + signer_key + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true + AND name in ('stack-stx', 'stacks-increase', 'stack-extend') + AND start_cycle_id <= ${args.cycle_number} + AND end_cycle_id > ${args.cycle_number} + ORDER BY stacker, block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC + ) + SELECT + ps.signing_key, + ps.weight, + ps.stacked_amount, + ps.weight_percent, + ps.stacked_amount_percent, + COUNT(DISTINCT ds.stacker)::int AS pooled_stacker_count, + COUNT(DISTINCT ss.stacker)::int AS solo_stacker_count + FROM pox_sets ps + LEFT JOIN delegated_stackers ds ON ps.signing_key = ds.signer_key + LEFT JOIN solo_stackers ss ON ps.signing_key = ss.signer_key + WHERE ps.canonical = TRUE + AND ps.cycle_number = ${args.cycle_number} + AND ps.signing_key = ${args.signer_key} + GROUP BY ps.signing_key, ps.weight, ps.stacked_amount, ps.weight_percent, ps.stacked_amount_percent LIMIT 1 `; if (results.count > 0) return results[0]; @@ -687,19 +769,68 @@ export class PgStoreV2 extends BasePgStoreModule { InvalidRequestErrorType.invalid_param ); const results = await sql<(DbPoxCycleSignerStacker & { total: number })[]>` - WITH stackers AS ( - SELECT DISTINCT ON (stacker) stacker, locked, pox_addr, amount_ustx, name - FROM pox4_events - WHERE canonical = true - AND microblock_canonical = true - AND start_cycle_id <= ${args.cycle_number} - AND (end_cycle_id >= ${args.cycle_number} OR end_cycle_id IS NULL) - AND signer_key = ${args.signer_key} - ORDER BY stacker, block_height DESC, tx_index DESC, event_index DESC + WITH signer_keys AS ( + SELECT DISTINCT ON (stacker) stacker, signer_key + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true + AND name in ('stack-aggregation-commit-indexed', 'stack-aggregation-commit') + AND start_cycle_id = ${args.cycle_number} + AND end_cycle_id = ${args.cycle_number + 1} + ORDER BY stacker, block_height DESC, tx_index DESC, event_index DESC + ), delegated_stackers AS ( + SELECT DISTINCT ON (main.stacker) + main.stacker, + sk.signer_key, + main.locked, + main.pox_addr, + main.name, + main.amount_ustx, + 'pooled' as stacker_type + FROM pox4_events main + LEFT JOIN signer_keys sk ON main.delegator = sk.stacker + WHERE main.canonical = true + AND main.microblock_canonical = true + AND main.name IN ('delegate-stack-stx', 'delegate-stack-increase', 'delegate-stack-extend') + AND main.start_cycle_id <= ${args.cycle_number} + AND main.end_cycle_id > ${args.cycle_number} + ORDER BY main.stacker, main.block_height DESC, main.microblock_sequence DESC, main.tx_index DESC, main.event_index DESC + ), solo_stackers AS ( + SELECT DISTINCT ON (stacker) + stacker, + signer_key, + locked, + pox_addr, + name, + amount_ustx, + 'solo' as stacker_type + FROM pox4_events + WHERE canonical = true AND microblock_canonical = true + AND name in ('stack-stx', 'stacks-increase', 'stack-extend') + AND start_cycle_id <= ${args.cycle_number} + AND end_cycle_id > ${args.cycle_number} + ORDER BY stacker, block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC + ), combined_stackers AS ( + SELECT * FROM delegated_stackers + UNION ALL + SELECT * FROM solo_stackers ) - SELECT *, COUNT(*) OVER()::int AS total FROM stackers - OFFSET ${offset} + SELECT + ps.signing_key, + cs.stacker, + cs.locked, + cs.pox_addr, + cs.name, + cs.amount_ustx, + cs.stacker_type, + COUNT(*) OVER()::int AS total + FROM pox_sets ps + LEFT JOIN combined_stackers cs ON ps.signing_key = cs.signer_key + WHERE ps.canonical = TRUE + AND ps.cycle_number = ${args.cycle_number} + AND ps.signing_key = ${args.signer_key} + ORDER BY locked DESC LIMIT ${limit} + OFFSET ${offset} `; return { limit, diff --git a/src/tests/pox-tests.ts b/src/tests/pox-tests.ts index 140477fe8..f0d70285e 100644 --- a/src/tests/pox-tests.ts +++ b/src/tests/pox-tests.ts @@ -111,6 +111,8 @@ describe('PoX tests', () => { stacked_amount_percent: 50, weight: 5, weight_percent: 55.55555555555556, + pooled_stacker_count: 0, + solo_stacker_count: 1, }, { signing_key: '0x029874497a7952483aa23890e9d0898696f33864d3df90939930a1f45421fe3b09', @@ -119,6 +121,8 @@ describe('PoX tests', () => { stacked_amount_percent: 33.333333333333336, weight: 3, weight_percent: 33.33333333333333, + pooled_stacker_count: 0, + solo_stacker_count: 1, }, { signing_key: '0x02dcde79b38787b72d8e5e0af81cffa802f0a3c8452d6b46e08859165f49a72736', @@ -127,6 +131,8 @@ describe('PoX tests', () => { stacked_amount_percent: 16.666666666666668, weight: 1, weight_percent: 11.11111111111111, + pooled_stacker_count: 0, + solo_stacker_count: 1, }, ], total: 3, @@ -143,6 +149,8 @@ describe('PoX tests', () => { stacked_amount_percent: 50, weight: 5, weight_percent: 55.55555555555556, + pooled_stacker_count: 0, + solo_stacker_count: 1, }); const stackers = await supertest(api.server).get( `/extended/v2/pox/cycles/14/signers/0x038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d/stackers` @@ -157,6 +165,7 @@ describe('PoX tests', () => { pox_address: 'mk4zAE1iVWf5PJAgeX83rSXnzF5zQBiqf1', stacked_amount: '686251350000000000', stacker_address: 'STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP', + stacker_type: 'solo', }, ], total: 1, @@ -262,6 +271,8 @@ describe('PoX tests', () => { stacked_amount_percent: 42.857142857142854, weight: 9, weight_percent: 42.857142857142854, + pooled_stacker_count: 0, + solo_stacker_count: 1, }, { signing_key: '0x023f19d77c842b675bd8c858e9ac8b0ca2efa566f17accf8ef9ceb5a992dc67836', @@ -270,6 +281,8 @@ describe('PoX tests', () => { stacked_amount_percent: 28.571428571428573, weight: 6, weight_percent: 28.57142857142857, + pooled_stacker_count: 0, + solo_stacker_count: 1, }, { // steph doubled the weight of this signer @@ -279,6 +292,8 @@ describe('PoX tests', () => { stacked_amount_percent: 28.571428571428573, weight: 6, weight_percent: 28.57142857142857, + pooled_stacker_count: 0, + solo_stacker_count: 2, }, ], total: 3, @@ -296,6 +311,8 @@ describe('PoX tests', () => { stacked_amount_percent: 28.571428571428573, weight: 6, weight_percent: 28.57142857142857, + pooled_stacker_count: 0, + solo_stacker_count: 2, }); const stackers = await supertest(api.server).get( @@ -311,11 +328,13 @@ describe('PoX tests', () => { pox_address: 'n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw', stacked_amount: '2500170000000000', stacker_address: 'ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP', // signer + stacker_type: 'solo', }, { pox_address: 'mhYeZXrSEuyf2wbJ14qZ2apG7ofMLDj9Ss', stacked_amount: '2500170000000000', stacker_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6', // steph + stacker_type: 'solo', }, ], total: 2,