Skip to content

Commit

Permalink
votes for action ids endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
ecioppettini committed Mar 28, 2024
1 parent 717e8b1 commit ae6950d
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Body, Controller, TsoaResponse, Res, Post, Route, SuccessResponse } from 'tsoa';
import { StatusCodes } from 'http-status-codes';
import tx from 'pg-tx';
import pool from '../services/PgPoolSingleton';
import type { ErrorShape } from '../../../shared/errors';
import { genErrorMessage } from '../../../shared/errors';
import { Errors } from '../../../shared/errors';
import type { EndpointTypes } from '../../../shared/routes';
import { Routes } from '../../../shared/routes';
import { resolveUntilTransaction } from '../services/PaginationService';
import { expectType } from 'tsd';
import { governanceCredentialDidVote } from '../services/GovernanceCredentialVotesByActionIds';
import { GOVERNANCE_VOTES_BY_GOV_IDS_LIMIT } from '../../../shared/constants';

const route = Routes.governanceCredentialVotesByGovActionId;

@Route('governance/credential/votesByGovId')
export class GovernanceCredentialVotesByGovId extends Controller {
/**
* Gets votes cast for a set of governance action ids.
*/
@SuccessResponse(`${StatusCodes.OK}`)
@Post()
public async governanceCredentialDidVote(
@Body()
requestBody: EndpointTypes[typeof route]['input'],
@Res()
errorResponse: TsoaResponse<
StatusCodes.BAD_REQUEST | StatusCodes.CONFLICT | StatusCodes.UNPROCESSABLE_ENTITY,
ErrorShape
>
): Promise<EndpointTypes[typeof route]['response']> {

if (requestBody.actionIds.length > GOVERNANCE_VOTES_BY_GOV_IDS_LIMIT.MAX_ACTION_IDS) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return errorResponse(
StatusCodes.BAD_REQUEST,
genErrorMessage(Errors.GovActionIdsLimitExceeded, {
limit: GOVERNANCE_VOTES_BY_GOV_IDS_LIMIT.MAX_ACTION_IDS,
found: requestBody.actionIds.length,
})
);
}

let credential = Buffer.from(requestBody.credential, 'hex');

const response = await tx<EndpointTypes[typeof route]['response'] | ErrorShape>(
pool,
async dbTx => {
const until = await resolveUntilTransaction({
block_hash: Buffer.from(requestBody.untilBlock, 'hex'),
dbTx,
});

if (until == null) {
return genErrorMessage(Errors.BlockHashNotFound, {
untilBlock: requestBody.untilBlock,
});
}

if(requestBody.actionIds.length === 0) {
return [];
}

const data = await governanceCredentialDidVote({
credential,
govActionIds: requestBody.actionIds.map(actionId => Buffer.from(actionId, 'hex')),
until: until.tx_id,
dbTx,
});

return data.map(vote => ({
actionId: vote.govActionId.toString('hex'),
txId: vote.txId,
payload: vote.vote.toString('hex'),
}));
}
);

if ('code' in response) {
expectType<Equals<typeof response, ErrorShape>>(true);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return errorResponse(StatusCodes.CONFLICT, response);
}

return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { GovernanceVotesForAddressResponse } from '../../../shared/models/Govern

const route = Routes.governanceVotesForAddress;

@Route('governance/votes/address')
@Route('governance/credential/votes')
export class GovernanceVotesForAddress extends Controller {
/**
* Returns the drep of the last delegation for this address.
Expand Down
17 changes: 7 additions & 10 deletions webserver/server/app/models/governance/votesForAddress.queries.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/** Types generated for queries found in "app/models/governance/votesForAddress.sql" */
import { PreparedQuery } from '@pgtyped/runtime';

export type BufferArray = (Buffer)[];

export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };

export type NumberOrString = number | string;
Expand Down Expand Up @@ -59,16 +57,16 @@ export const votesForAddress = new PreparedQuery<IVotesForAddressParams,IVotesFo

/** 'DidVote' parameters type */
export interface IDidVoteParams {
gov_action_ids: readonly (BufferArray)[];
limit: NumberOrString;
gov_action_ids: readonly (Buffer)[];
until_tx_id: NumberOrString;
voter: Buffer;
}

/** 'DidVote' return type */
export interface IDidVoteResult {
gov_action_id: Buffer | null;
vote: Buffer | null;
govActionId: Buffer;
txId: string;
vote: Buffer;
}

/** 'DidVote' query type */
Expand All @@ -77,20 +75,19 @@ export interface IDidVoteQuery {
result: IDidVoteResult;
}

const didVoteIR: any = {"usedParamSet":{"until_tx_id":true,"voter":true,"gov_action_ids":true,"limit":true},"params":[{"name":"gov_action_ids","required":true,"transform":{"type":"array_spread"},"locs":[{"a":207,"b":222}]},{"name":"until_tx_id","required":true,"transform":{"type":"scalar"},"locs":[{"a":141,"b":153}]},{"name":"voter","required":true,"transform":{"type":"scalar"},"locs":[{"a":171,"b":177}]},{"name":"limit","required":true,"transform":{"type":"scalar"},"locs":[{"a":257,"b":263}]}],"statement":"SELECT gov_action_id, vote\nFROM \"GovernanceVote\"\nJOIN \"Transaction\" ON \"GovernanceVote\".tx_id = \"Transaction\".id\nWHERE\n\t\"Transaction\".id <= :until_tx_id! AND\n voter = :voter! AND\n gov_action_id = ANY(:gov_action_ids!)\nORDER BY \"Transaction\".id\nLIMIT :limit!"};
const didVoteIR: any = {"usedParamSet":{"until_tx_id":true,"voter":true,"gov_action_ids":true},"params":[{"name":"gov_action_ids","required":true,"transform":{"type":"array_spread"},"locs":[{"a":262,"b":277}]},{"name":"until_tx_id","required":true,"transform":{"type":"scalar"},"locs":[{"a":199,"b":211}]},{"name":"voter","required":true,"transform":{"type":"scalar"},"locs":[{"a":229,"b":235}]}],"statement":"SELECT gov_action_id as \"govActionId!\", vote as \"vote!\", \"Transaction\".id as \"txId!\"\nFROM \"GovernanceVote\"\nJOIN \"Transaction\" ON \"GovernanceVote\".tx_id = \"Transaction\".id\nWHERE\n\t\"Transaction\".id <= :until_tx_id! AND\n voter = :voter! AND\n gov_action_id IN :gov_action_ids!\nORDER BY \"Transaction\".id"};

/**
* Query generated from SQL:
* ```
* SELECT gov_action_id, vote
* SELECT gov_action_id as "govActionId!", vote as "vote!", "Transaction".id as "txId!"
* FROM "GovernanceVote"
* JOIN "Transaction" ON "GovernanceVote".tx_id = "Transaction".id
* WHERE
* "Transaction".id <= :until_tx_id! AND
* voter = :voter! AND
* gov_action_id = ANY(:gov_action_ids!)
* gov_action_id IN :gov_action_ids!
* ORDER BY "Transaction".id
* LIMIT :limit!
* ```
*/
export const didVote = new PreparedQuery<IDidVoteParams,IDidVoteResult>(didVoteIR);
Expand Down
7 changes: 3 additions & 4 deletions webserver/server/app/models/governance/votesForAddress.sql
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ LIMIT :limit!;
@name didVote
@param gov_action_ids -> (...)
*/
SELECT gov_action_id, vote
SELECT gov_action_id as "govActionId!", vote as "vote!", "Transaction".id as "txId!"
FROM "GovernanceVote"
JOIN "Transaction" ON "GovernanceVote".tx_id = "Transaction".id
WHERE
"Transaction".id <= :until_tx_id! AND
voter = :voter! AND
gov_action_id = ANY(:gov_action_ids!)
ORDER BY "Transaction".id
LIMIT :limit!;
gov_action_id IN :gov_action_ids!
ORDER BY "Transaction".id;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { PoolClient } from 'pg';
import {
didVote,
IDidVoteResult,
} from '../models/governance/votesForAddress.queries';

export async function governanceCredentialDidVote(request: {
credential: Buffer;
govActionIds: Buffer[];
dbTx: PoolClient;
until: number;
}): Promise<IDidVoteResult[]> {
return (
await didVote.run(
{
voter: request.credential,
gov_action_ids: request.govActionIds,
until_tx_id: request.until,
},
request.dbTx
)
);
}

4 changes: 4 additions & 0 deletions webserver/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ export const MINT_BURN_HISTORY_LIMIT = {

export const GOVERNANCE_VOTES_LIMIT = {
DEFAULT_PAGE_SIZE: 50,
};

export const GOVERNANCE_VOTES_BY_GOV_IDS_LIMIT = {
MAX_ACTION_IDS: 50,
};
6 changes: 6 additions & 0 deletions webserver/shared/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ export const Errors = {
detailsGen: (details: { limit: number; found: number }) =>
`Limit of ${details.limit}, found ${details.found}`,
},
GovActionIdsLimitExceeded: {
code: ErrorCodes.AssetLimitExceeded,
prefix: "Exceeded request governance action ids limit.",
detailsGen: (details: { limit: number; found: number }) =>
`Limit of ${details.limit}, found ${details.found}`,
},
} as const;

export function genErrorMessage<T extends typeof Errors[keyof typeof Errors]>(
Expand Down
14 changes: 13 additions & 1 deletion webserver/shared/models/Governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ import { AfterBlockPagination, UntilBlockPagination } from "./common";
export type GovernanceVotesForAddressRequest = {
credential: CredentialHex;
limit?: number | undefined;
} & UntilBlockPagination & AfterBlockPagination;
} & UntilBlockPagination &
AfterBlockPagination;

export type GovernanceVotesForAddressResponse = {
votes: { govActionId: string; vote: string }[];
txId: string;
block: string;
}[];

export type GovernanceCredentialDidVoteRequest = {
credential: CredentialHex;
actionIds: string[];
} & UntilBlockPagination;

export type GovernanceCredentialDidVoteResponse = {
actionId: string;
txId: string;
payload: string;
}[];
10 changes: 8 additions & 2 deletions webserver/shared/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import type {
MintBurnHistoryRequest,
MintBurnHistoryResponse,
} from "./models/MintBurn";
import { GovernanceVotesForAddressRequest, GovernanceVotesForAddressResponse } from "./models/Governance";
import { GovernanceCredentialDidVoteRequest, GovernanceCredentialDidVoteResponse, GovernanceVotesForAddressRequest, GovernanceVotesForAddressResponse } from "./models/Governance";

export enum Routes {
transactionHistory = "transaction/history",
Expand All @@ -55,7 +55,8 @@ export enum Routes {
assetUtxos = "asset/utxos",
mintBurnHistory = "asset/mint-burn-history",
drepDelegationForAddress = "delegation/drep/address",
governanceVotesForAddress = "governance/votes/address",
governanceVotesForAddress = "governance/credential/votes",
governanceCredentialVotesByGovActionId = "governance/credential/votesByGovId",
}

export type EndpointTypes = {
Expand Down Expand Up @@ -139,4 +140,9 @@ export type EndpointTypes = {
input: GovernanceVotesForAddressRequest;
response: GovernanceVotesForAddressResponse;
};
[Routes.governanceCredentialVotesByGovActionId]: {
name: typeof Routes.governanceCredentialVotesByGovActionId;
input: GovernanceCredentialDidVoteRequest;
response: GovernanceCredentialDidVoteResponse;
};
};

0 comments on commit ae6950d

Please sign in to comment.