Skip to content

Commit

Permalink
feat!: add and implement new stake pool sorting options
Browse files Browse the repository at this point in the history
  • Loading branch information
iccicci committed Feb 6, 2024
1 parent 2cd1e01 commit bcc5e80
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ProviderError,
ProviderFailure,
QueryStakePoolsArgs,
SortField,
StakePoolProvider,
StakePoolStats
} from '@cardano-sdk/core';
Expand Down Expand Up @@ -80,6 +81,8 @@ export class DbSyncStakePoolProvider extends DbSyncProvider(RunnableModule) impl
#responseConfig: StakePoolProviderProps['responseConfig'];
#useBlockfrost: boolean;

static notSupportedSortFields: SortField[] = ['blocks', 'lastRos', 'liveStake', 'margin', 'pledge', 'ros'];

constructor(
{ paginationPageSizeLimit, responseConfig, useBlockfrost }: StakePoolProviderProps,
{ cache, dbPools, cardanoNode, genesisData, metadataService, logger, epochMonitor }: StakePoolProviderDependencies
Expand Down Expand Up @@ -133,12 +136,6 @@ export class DbSyncStakePoolProvider extends DbSyncProvider(RunnableModule) impl
}

return (options: QueryStakePoolsArgs) => this.#builder.queryPoolAPY(hashesIds, this.#epochLength, options);
case 'ros':
throw new ProviderError(
ProviderFailure.NotImplemented,
null,
'DbSyncStakePoolProvider do not support sort by ROS'
);
case 'data':
default:
return (options: QueryStakePoolsArgs) => this.#builder.queryPoolData(updatesIds, useBlockfrost, options);
Expand Down Expand Up @@ -258,7 +255,7 @@ export class DbSyncStakePoolProvider extends DbSyncProvider(RunnableModule) impl
}

public async queryStakePools(options: QueryStakePoolsArgs): Promise<Paginated<Cardano.StakePool>> {
const { filters, pagination, apyEpochsBackLimit = APY_EPOCHS_BACK_LIMIT_DEFAULT } = options;
const { filters, pagination, sort, apyEpochsBackLimit = APY_EPOCHS_BACK_LIMIT_DEFAULT } = options;
const useBlockfrost = this.#useBlockfrost;

if (pagination.limit > this.#paginationPageSizeLimit) {
Expand All @@ -279,6 +276,14 @@ export class DbSyncStakePoolProvider extends DbSyncProvider(RunnableModule) impl
);
}

if (DbSyncStakePoolProvider.notSupportedSortFields.includes(sort?.field || 'name')) {
throw new ProviderError(
ProviderFailure.NotImplemented,
undefined,
`DbSyncStakePoolProvider doesn't support sort by ${sort?.field} `
);
}

const { params, query } = useBlockfrost
? this.#builder.buildBlockfrostQuery(filters)
: filters?._condition === 'or'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,15 @@ export const stakePoolSearchSelection = [

export const sortSelectionMap: { [key in SortField]: string } = {
apy: 'metrics_ros',
blocks: 'metrics_minted_blocks',
cost: 'params.cost',
lastRos: 'metrics_last_ros',
liveStake: 'metrics_live_stake',
// PERF: this may be source of performances issue due to its complexity.
// In case of performances degradation we need to keep in mind this.
margin: "(margin->>'numerator')::numeric / (margin->>'denominator')::numeric",
name: 'metadata.name',
pledge: 'params_pledge',
ros: 'metrics_ros',
saturation: 'metrics_live_saturation'
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,14 @@ describe('StakePoolHttpService', () => {
}, dbPools.main)
);

it.each(DbSyncStakePoolProvider.notSupportedSortFields)(
"Doesn't support sorting by %s",
async (field) =>
await expect(provider.queryStakePools({ pagination, sort: { field, order: 'asc' } })).rejects.toThrow(
`DbSyncStakePoolProvider doesn't support sort by ${field}`
)
);

describe('pagination', () => {
const baseArgs = { pagination: { limit: 2, startAt: 0 } };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,125 @@ describe('TypeormStakePoolProvider', () => {
expect(response.pageResults[0].metrics?.ros).toEqual(expectedRos.min);
});
});

describe('sort by margin', () => {
let margins: Cardano.Fraction[] = [];

beforeAll(() => {
margins = poolsInfo
.map(({ margin }) => margin)
.sort((a, b) => (a.numerator / a.denominator < b.numerator / b.denominator ? -1 : 1));
});

it('desc order', async () => {
const { pageResults } = await provider.queryStakePools(
setSortCondition({ pagination }, 'desc', 'margin')
);

expect(pageResults.map(({ margin }) => margin)).toEqual(
margins
.map((_) => _)
.reverse()
.splice(0, 10)
);
});

it('asc order', async () => {
const { pageResults } = await provider.queryStakePools(setSortCondition({ pagination }, 'asc', 'margin'));

expect(pageResults.map(({ margin }) => margin)).toEqual(margins.map((_) => _).splice(0, 10));
});
});

describe('sort by pledge', () => {
let pledges: bigint[] = [];

beforeAll(() => {
pledges = poolsInfo.map(({ pledge }) => BigInt(pledge)).sort((a, b) => (a < b ? -1 : 1));
});

it('desc order', async () => {
const { pageResults } = await provider.queryStakePools(
setSortCondition({ pagination }, 'desc', 'pledge')
);

expect(pageResults.map(({ pledge }) => pledge)).toEqual(
pledges
.map((_) => _)
.reverse()
.splice(0, 10)
);
});

it('asc order', async () => {
const { pageResults } = await provider.queryStakePools(setSortCondition({ pagination }, 'asc', 'pledge'));

expect(pageResults.map(({ pledge }) => pledge)).toEqual(pledges.map((_) => _).splice(0, 10));
});
});

describe('sort by blocks', () => {
let blocks: number[] = [];

beforeAll(() => {
blocks = poolsInfoWithMetrics.map((_) => _.blocks).sort((a, b) => (a < b ? -1 : 1));
});

it('desc order', async () => {
const { pageResults } = await provider.queryStakePools(
setSortCondition({ pagination }, 'desc', 'blocks')
);

expect(pageResults.map(({ metrics }) => metrics!.blocksCreated)).toEqual(
blocks
.map((_) => _)
.reverse()
.splice(0, 10)
);
});

it('asc order', async () => {
const { pageResults } = await provider.queryStakePools(setSortCondition({ pagination }, 'asc', 'blocks'));

expect(pageResults.map(({ metrics }) => metrics!.blocksCreated)).toEqual(
blocks.filter((_) => _ !== null).splice(0, 10)
);
});
});

describe('sort by liveStake', () => {
let pledges: bigint[] = [];

beforeAll(() => {
pledges = poolsInfoWithMetrics
.filter(({ stake }) => stake !== null)
.map(({ stake }) => BigInt(stake))
.sort((a, b) => (a < b ? -1 : 1));
});

it('desc order', async () => {
const { pageResults } = await provider.queryStakePools(
setSortCondition({ pagination }, 'desc', 'liveStake')
);

expect(pageResults.map(({ metrics }) => metrics!.stake.live)).toEqual(
pledges
.map((_) => _)
.reverse()
.splice(0, 10)
);
});

it('asc order', async () => {
const { pageResults } = await provider.queryStakePools(
setSortCondition({ pagination }, 'asc', 'liveStake')
);

expect(pageResults.map(({ metrics }) => metrics!.stake.live)).toEqual(
pledges.map((_) => _).splice(0, 10)
);
});
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export type PoolInfo = {
apy?: number;
lastRos?: number;
ros?: number;
pledge: string;
blocks: number;
stake: string;
margin: Cardano.Fraction;
};

export type PoolFixtureModel = {
Expand All @@ -29,6 +33,10 @@ export type PoolFixtureModel = {
live_saturation: string;
ros?: string;
last_ros?: string;
pledge: string;
blocks: number;
stake: string;
margin: Cardano.Fraction;
};

export class TypeormStakePoolFixtureBuilder {
Expand All @@ -54,16 +62,36 @@ export class TypeormStakePoolFixtureBuilder {
this.#logger.warn(`${desiredQty} pools desired, only ${resultsQty} results found`);
}

return result.rows.map(({ id, status, name, ticker, cost, ros, last_ros, live_saturation, metadata_url }) => ({
cost,
id: id as unknown as Cardano.PoolId,
lastRos: typeof last_ros === 'string' ? Number.parseFloat(last_ros) : undefined,
metadataUrl: metadata_url,
name,
ros: typeof ros === 'string' ? Number.parseFloat(ros) : undefined,
saturation: Number.parseFloat(live_saturation),
status,
ticker
}));
return result.rows.map(
({
id,
status,
name,
ticker,
cost,
ros,
last_ros,
live_saturation,
metadata_url,
pledge,
blocks,
stake,
margin
}) => ({
blocks,
cost,
id: id as unknown as Cardano.PoolId,
lastRos: typeof last_ros === 'string' ? Number.parseFloat(last_ros) : undefined,
margin,
metadataUrl: metadata_url,
name,
pledge,
ros: typeof ros === 'string' ? Number.parseFloat(ros) : undefined,
saturation: Number.parseFloat(live_saturation),
stake,
status,
ticker
})
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ export const withMeta = (withMetadata: boolean) => `AND pr.metadata_url IS ${wit

// Query returns all pools except delisted
export const findStakePools = (hasMetadata = false) => `
SELECT
sp.id, sp.status, pr.reward_account, pr.pledge,
pr.cost, pr.margin,pr.relays, pr.owners, pr.vrf,
pr.metadata_url, pr.metadata_hash, pr.block_slot,
pm.name, pm.ticker, pm.description, pm.homepage,
pm.ext, cpm.live_saturation, cpm.last_ros, cpm.ros
SELECT
sp.id, sp.status, pr.reward_account, pr.pledge,
pr.cost, pr.margin, pr.relays, pr.owners, pr.vrf,
pr.metadata_url, pr.metadata_hash, pr.block_slot,
pm.name, pm.ticker, pm.description, pm.homepage,
pm.ext, cpm.live_saturation, cpm.last_ros, cpm.ros,
cpm.minted_blocks AS blocks, cpm.live_stake AS stake
FROM public.stake_pool as sp
LEFT JOIN pool_registration as pr ON sp.last_registration_id = pr.id
LEFT JOIN pool_metadata as pm ON pr.id = pm.pool_update_id
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/Provider/StakePoolProvider/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export const PoolDataSortFields = ['name', 'cost'] as const;
export const PoolMetricsSortFields = ['saturation'] as const;
export const PoolDataSortFields = ['cost', 'name', 'margin', 'pledge'] as const;
export const PoolMetricsSortFields = ['blocks', 'liveStake', 'saturation'] as const;
export const PoolAPYSortFields = ['apy'] as const;
export const PoolROSSortFields = ['ros', 'lastRos'] as const;

Expand Down

0 comments on commit bcc5e80

Please sign in to comment.