Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IND-470] Update asset create handler to use SQL function. #748

Merged
merged 1 commit into from
Nov 3, 2023
Merged

Conversation

lcwik
Copy link
Contributor

@lcwik lcwik commented Nov 3, 2023

Changelist

[IND-470] Update asset create handler to use SQL function.

Test Plan

Updated existing tests.

Author/Reviewer Checklist

  • If this PR has changes that result in a different app state given the same prior state and transaction list, manually add the state-breaking label.
  • If this PR isn't state-breaking but has changes that modify behavior in PrepareProposal or ProcessProposal, manually add the label proposal-breaking.
  • If this PR is one of many that implement a specific feature, manually label them all feature:[feature-name].
  • If you wish to for mergify-bot to automatically create a PR to backport your change to a release branch, manually add the label backport/[branch-name].
  • Manually add any of the following labels: refactor, chore, bug.

Copy link

linear bot commented Nov 3, 2023

IND-470 Ender asset create handler SQL pushdown

Update ender asset create to execute updates via a SQL function.

Copy link
Contributor

coderabbitai bot commented Nov 3, 2023

Walkthrough

The changes introduce a new way to handle asset creation events in the indexer service. The AssetModel now includes a method for SQL to JSON conversions. The asset creation handler can now use either a SQL function or Knex based on a configuration flag. A new SQL function dydx_asset_create_handler has been added to create assets and associate them with markets.

Changes

File(s) Summary
.../postgres/src/constants.ts, .../postgres/src/index.ts, .../postgres/src/models/asset-model.ts Added AssetModel to the list of models with SQL to JSON conversions. Exported AssetModel from the module. Added a new method sqlToJsonConversions to AssetModel.
.../ender/__tests__/handlers/asset-handler.test.ts Updated tests to include new configuration and parameterized testing. Added assetRefresher.clear() in the afterEach block. Modified error messages in assertions.
.../ender/src/config.ts Renamed USE_ORDER_HANDLER_SQL_FUNCTION to USE_ASSET_CREATE_HANDLER_SQL_FUNCTION and added a new property USE_ORDER_HANDLER_SQL_FUNCTION to the configSchema object.
.../ender/src/handlers/asset-handler.ts Updated AssetCreationHandler to handle asset creation events using either a SQL function or Knex based on a configuration flag. Added new methods handleViaSqlFunction() and handleViaKnex().
.../ender/src/helpers/postgres/postgres-functions.ts Added a new script dydx_asset_create_handler.sql to the list of scripts.
.../ender/src/scripts/dydx_asset_create_handler.sql Added a new SQL function dydx_asset_create_handler that creates a new asset record and associates it with a market.

Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • If you reply to a review comment from CodeRabbit, the bot will automatically respond.
  • To engage with CodeRabbit bot directly around the specific lines of code in the PR, mention @coderabbitai in your review comment
  • Note: Review comments are made on code diffs or files, not on the PR overview.
  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai help to get help.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.

Note: For conversation with the bot, please use the review comments on code diffs or files.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 3

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 5e0c4e0 and e86ac98.
Files selected for processing (8)
  • indexer/packages/postgres/src/constants.ts (2 hunks)
  • indexer/packages/postgres/src/index.ts (1 hunks)
  • indexer/packages/postgres/src/models/asset-model.ts (1 hunks)
  • indexer/services/ender/tests/handlers/asset-handler.test.ts (3 hunks)
  • indexer/services/ender/src/config.ts (2 hunks)
  • indexer/services/ender/src/handlers/asset-handler.ts (2 hunks)
  • indexer/services/ender/src/helpers/postgres/postgres-functions.ts (1 hunks)
  • indexer/services/ender/src/scripts/dydx_asset_create_handler.sql (1 hunks)
Files skipped from review due to trivial changes (2)
  • indexer/packages/postgres/src/constants.ts
  • indexer/packages/postgres/src/index.ts
Additional comments: 10
indexer/packages/postgres/src/models/asset-model.ts (1)
  • 59-76: The new sqlToJsonConversions static method provides a mapping from column names to JSON conversion types. This is a good practice for ensuring data consistency when interacting with the database. However, it's important to ensure that these conversions align with the actual data types in the database. For instance, if id is an integer in the database, converting it to a string might lead to unexpected behavior.
indexer/services/ender/src/config.ts (2)
  • 23-28: The new configuration USE_ASSET_CREATE_HANDLER_SQL_FUNCTION is introduced and set to true by default. Ensure that this change is reflected in the environment configuration files and the deployment pipeline.

  • 41-43: The configuration USE_ORDER_HANDLER_SQL_FUNCTION is set to true by default. Make sure that this change is also reflected in the environment configuration files and the deployment pipeline.

indexer/services/ender/src/helpers/postgres/postgres-functions.ts (1)
  • 29-35: The addition of the new SQL script dydx_asset_create_handler.sql to the list of scripts is appropriate given the context of the PR. Ensure that the script is correctly implemented and tested.
indexer/services/ender/src/handlers/asset-handler.ts (4)
  • 1-16: Imports look fine. Ensure that all the imported modules are being used in the code.

  • 23-30: The internalHandle method now checks the USE_ASSET_CREATE_HANDLER_SQL_FUNCTION config flag and calls the appropriate handler method. This is a good approach to switch between different methods of handling asset creation.

  • 32-53: The handleViaSqlFunction method executes a SQL query to handle the asset creation event and adds the asset to assetRefresher. Ensure that the SQL function dydx_asset_create_handler exists and works as expected. Also, make sure that the error handling is adequate and that the error message provides enough context for debugging.

  • 55-58: The handleViaKnex method calls the createAsset method with timing stats and error logging. Ensure that the runFuncWithTimingStatAndErrorLogging function and the createAsset method work as expected.

indexer/services/ender/src/scripts/dydx_asset_create_handler.sql (1)
  • 1-34: The SQL function dydx_asset_create_handler seems to be well written and follows good practices. However, there are a few points to consider:
  1. Error handling: The function raises an exception when the market ID is not found. This is good, but it might be beneficial to also handle other potential errors, such as insertion failures.

  2. Transaction handling: The function does not seem to use explicit transactions. If multiple operations need to be performed atomically, consider wrapping them in a transaction.

  3. SQL Injection: The function seems to be safe from SQL injection as it uses parameterized queries.

  4. Performance: The function performs a SELECT operation to retrieve the market ID. If the markets table is large, this could be a performance bottleneck. Consider indexing the id column in the markets table if it's not already indexed.

  5. Maintainability: The function is quite readable and maintainable. It uses clear variable names and is well-structured.

Please verify these points and make changes if necessary.

indexer/services/ender/__tests__/handlers/asset-handler.test.ts (1)
  • 62-67: The addition of assetRefresher.clear() in the afterEach block is a good practice to ensure that the state from one test does not leak into another.

Comment on lines 36 to 42
} from '../helpers/constants';
import { updateBlockCache } from '../../src/caches/block-cache';
import { createPostgresFunctions } from '../../src/helpers/postgres/postgres-functions';
import config from '../../src/config';

describe('assetHandler', () => {
beforeAll(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import of config and the use of config.USE_ASSET_CREATE_HANDLER_SQL_FUNCTION in the tests indicate that the tests are now dependent on the configuration. This could lead to unpredictable test results if the configuration changes. Consider mocking the configuration to ensure consistent test results.

Comment on lines 100 to 131
});
});

it('fails when market doesnt exist for asset', async () => {
const transactionIndex: number = 0;
const kafkaMessage: KafkaMessage = createKafkaMessageFromAssetEvent({
assetEvent: defaultAssetCreateEvent,
transactionIndex,
height: defaultHeight,
time: defaultTime,
txHash: defaultTxHash,
});

const message: string = 'Unable to find market with id: 0';
await expect(onMessage(kafkaMessage)).rejects.toThrowError(
new Error(message),
);
});
it.each([
[
'via knex',
false,
],
[
'via SQL function',
true,
],
])(
'fails when market doesnt exist for asset (%s)',
async (
_name: string,
useSqlFunction: boolean,
) => {
config.USE_ASSET_CREATE_HANDLER_SQL_FUNCTION = useSqlFunction;
const transactionIndex: number = 0;
const kafkaMessage: KafkaMessage = createKafkaMessageFromAssetEvent({
assetEvent: defaultAssetCreateEvent,
transactionIndex,
height: defaultHeight,
time: defaultTime,
txHash: defaultTxHash,
});

it('creates new asset', async () => {
await MarketTable.create(testConstants.defaultMarket);
await marketRefresher.updateMarkets();
const transactionIndex: number = 0;

const assetEvent: AssetCreateEventV1 = defaultAssetCreateEvent;
const kafkaMessage: KafkaMessage = createKafkaMessageFromAssetEvent({
assetEvent,
transactionIndex,
height: defaultHeight,
time: defaultTime,
txHash: defaultTxHash,
await expect(onMessage(kafkaMessage)).rejects.toThrowError(
'Unable to find market with id: 0',
);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameterized tests are a good way to test the same logic with different configurations. However, the configuration is being set directly in the test. This could lead to unpredictable test results if the tests are run in parallel. Consider setting the configuration in a beforeEach block within the describe.each block to ensure that the configuration is set correctly for each test.

- it.each([
-   [
-     'via knex',
-     false,
-   ],
-   [
-     'via SQL function',
-     true,
-   ],
- ])(
-   'fails when market doesnt exist for asset (%s)',
-   async (
-     _name: string,
-     useSqlFunction: boolean,
-   ) => {
-     config.USE_ASSET_CREATE_HANDLER_SQL_FUNCTION = useSqlFunction;
-     // rest of the test
-   });
+ describe.each([
+   [
+     'via knex',
+     false,
+   ],
+   [
+     'via SQL function',
+     true,
+   ],
+ ])('Asset creation %s', (_name: string, useSqlFunction: boolean) => {
+   beforeEach(() => {
+     config.USE_ASSET_CREATE_HANDLER_SQL_FUNCTION = useSqlFunction;
+   });
+
+   it('fails when market doesnt exist for asset', async () => {
+     // rest of the test
+   });
+ });

Commitable suggestion (Beta)
Suggested change
});
});
it('fails when market doesnt exist for asset', async () => {
const transactionIndex: number = 0;
const kafkaMessage: KafkaMessage = createKafkaMessageFromAssetEvent({
assetEvent: defaultAssetCreateEvent,
transactionIndex,
height: defaultHeight,
time: defaultTime,
txHash: defaultTxHash,
});
const message: string = 'Unable to find market with id: 0';
await expect(onMessage(kafkaMessage)).rejects.toThrowError(
new Error(message),
);
});
it.each([
[
'via knex',
false,
],
[
'via SQL function',
true,
],
])(
'fails when market doesnt exist for asset (%s)',
async (
_name: string,
useSqlFunction: boolean,
) => {
config.USE_ASSET_CREATE_HANDLER_SQL_FUNCTION = useSqlFunction;
const transactionIndex: number = 0;
const kafkaMessage: KafkaMessage = createKafkaMessageFromAssetEvent({
assetEvent: defaultAssetCreateEvent,
transactionIndex,
height: defaultHeight,
time: defaultTime,
txHash: defaultTxHash,
});
it('creates new asset', async () => {
await MarketTable.create(testConstants.defaultMarket);
await marketRefresher.updateMarkets();
const transactionIndex: number = 0;
const assetEvent: AssetCreateEventV1 = defaultAssetCreateEvent;
const kafkaMessage: KafkaMessage = createKafkaMessageFromAssetEvent({
assetEvent,
transactionIndex,
height: defaultHeight,
time: defaultTime,
txHash: defaultTxHash,
await expect(onMessage(kafkaMessage)).rejects.toThrowError(
'Unable to find market with id: 0',
);
});
describe.each([
[
'via knex',
false,
],
[
'via SQL function',
true,
],
])('Asset creation %s', (_name: string, useSqlFunction: boolean) => {
beforeEach(() => {
config.USE_ASSET_CREATE_HANDLER_SQL_FUNCTION = useSqlFunction;
});
it('fails when market doesnt exist for asset', async () => {
// rest of the test
});
});

Comment on lines +133 to +178
it.each([
[
'via knex',
false,
],
[
'via SQL function',
true,
],
])(
'creates new asset (%s)',
async (
_name: string,
useSqlFunction: boolean,
) => {
config.USE_ASSET_CREATE_HANDLER_SQL_FUNCTION = useSqlFunction;
await MarketTable.create(testConstants.defaultMarket);
await marketRefresher.updateMarkets();
const transactionIndex: number = 0;

const newAssets: AssetFromDatabase[] = await AssetTable.findAll(
{},
[], {
orderBy: [[AssetColumns.id, Ordering.ASC]],
const assetEvent: AssetCreateEventV1 = defaultAssetCreateEvent;
const kafkaMessage: KafkaMessage = createKafkaMessageFromAssetEvent({
assetEvent,
transactionIndex,
height: defaultHeight,
time: defaultTime,
txHash: defaultTxHash,
});
expect(newAssets.length).toEqual(1);
expectAssetMatchesEvent(assetEvent, newAssets[0]);
expectTimingStats();
const asset: AssetFromDatabase = assetRefresher.getAssetFromId('0');
expect(asset).toBeDefined();
});
// Confirm there is no existing asset to or from the sender subaccount
await expectNoExistingAssets();

await onMessage(kafkaMessage);

const newAssets: AssetFromDatabase[] = await AssetTable.findAll(
{},
[], {
orderBy: [[AssetColumns.id, Ordering.ASC]],
});
expect(newAssets.length).toEqual(1);
expectAssetMatchesEvent(assetEvent, newAssets[0]);
if (!useSqlFunction) {
expectTimingStats();
}
const asset: AssetFromDatabase = assetRefresher.getAssetFromId('0');
expect(asset).toBeDefined();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same comment applies to these parameterized tests as well.

@lcwik lcwik merged commit 4bd3c34 into main Nov 3, 2023
11 checks passed
@lcwik lcwik deleted the lukeind470 branch November 3, 2023 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

2 participants