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

transaction history: add input addresses, metadata and slots filter #173

Merged
merged 14 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/bin/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,16 @@
},
"TransactionInfo": {
"properties": {
"inputCredentials": {
"items": {
"type": "string"
},
"type": "array"
},
"metadata": {
"type": "string",
"nullable": true
},
"payload": {
"type": "string",
"description": "cbor-encoded transaction",
Expand Down Expand Up @@ -1166,10 +1176,35 @@
"description": "Filter which uses of the address are considered relevant for the query.\n\nThis is a bitmask, so you can combine multiple options\nex: `RelationFilterType.Input & RelationFilterType.Output`\n\nNote: relations only apply to credentials and not to full bech32 addresses",
"pattern": "([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"
},
"SlotLimits": {
"properties": {
"to": {
"type": "number",
"format": "double"
},
"from": {
"type": "number",
"format": "double"
}
},
"required": [
"to",
"from"
],
"type": "object"
},
"TransactionHistoryRequest": {
"allOf": [
{
"properties": {
"withInputContext": {
"type": "boolean",
"description": "If this is set to true, the result includes the input addresses (which are\nnot part of the tx), and the metadata (if any)"
},
"slotLimits": {
"$ref": "#/components/schemas/SlotLimits",
"description": "This limits the transactions in the result to this range of slots.\nEverything else is filtered out"
},
"limit": {
"type": "number",
"format": "double",
Expand Down
1 change: 1 addition & 0 deletions indexer/entity/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct Model {
pub epoch: i32,
pub slot: i32,
pub payload: Option<Vec<u8>>,
pub tx_count: i32,
}

#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
Expand Down
2 changes: 2 additions & 0 deletions indexer/migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod m20230223_000015_modify_block_table;
mod m20230927_000016_create_stake_delegation_table;
mod m20231025_000017_projected_nft;
mod m20231220_000018_asset_utxo_table;
mod m20240229_000019_add_block_tx_count_column;

pub struct Migrator;

Expand Down Expand Up @@ -47,6 +48,7 @@ impl MigratorTrait for Migrator {
Box::new(m20230927_000016_create_stake_delegation_table::Migration),
Box::new(m20231025_000017_projected_nft::Migration),
Box::new(m20231220_000018_asset_utxo_table::Migration),
Box::new(m20240229_000019_add_block_tx_count_column::Migration),
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use sea_schema::migration::prelude::*;

use entity::block::*;

pub struct Migration;

impl MigrationName for Migration {
fn name(&self) -> &str {
"m20240229_000019_add_block_tx_count_column"
}
}

#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Entity)
.add_column(ColumnDef::new(Column::TxCount).integer())
.to_owned(),
)
.await
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Entity)
.drop_column(Column::TxCount)
.to_owned(),
)
.await
}
}
1 change: 1 addition & 0 deletions indexer/tasks/src/genesis/genesis_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async fn handle_block(
epoch: Set(0),
slot: Set(0),
payload: Set(Some(block_payload)),
tx_count: Set(0),
..Default::default()
};

Expand Down
18 changes: 18 additions & 0 deletions indexer/tasks/src/multiera/multiera_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,25 @@ async fn handle_block(
epoch: Set(block.2.epoch.unwrap() as i32),
slot: Set(block.1.header().slot() as i32),
payload: Set(Some(block_payload)),
tx_count: Set(block_tx_count(block.1) as i32),
..Default::default()
};
block.insert(db_tx).await
}

fn block_tx_count(block: &cml_multi_era::MultiEraBlock) -> usize {
match block {
cml_multi_era::MultiEraBlock::Byron(
cml_multi_era::byron::block::ByronBlock::EpochBoundary(_),
) => 0,
cml_multi_era::MultiEraBlock::Byron(cml_multi_era::byron::block::ByronBlock::Main(
block,
)) => block.body.tx_payload.len(),
cml_multi_era::MultiEraBlock::Shelley(block) => block.transaction_bodies.len(),
cml_multi_era::MultiEraBlock::Allegra(block) => block.transaction_bodies.len(),
cml_multi_era::MultiEraBlock::Mary(block) => block.transaction_bodies.len(),
cml_multi_era::MultiEraBlock::Alonzo(block) => block.transaction_bodies.len(),
cml_multi_era::MultiEraBlock::Babbage(block) => block.transaction_bodies.len(),
cml_multi_era::MultiEraBlock::Conway(block) => block.transaction_bodies.len(),
}
}
2 changes: 1 addition & 1 deletion webserver/server/.nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v14.17.0
lts/hydrogen
42 changes: 39 additions & 3 deletions webserver/server/app/controllers/TransactionHistoryController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Routes } from '../../../shared/routes';
import sortBy from 'lodash/sortBy';
import { getAddressTypes } from '../models/utils';
import { RelationFilterType } from '../../../shared/models/common';
import { slotBoundsPagination } from '../models/pagination/slotBoundsPagination.queries';

const route = Routes.transactionHistory;

Expand All @@ -32,7 +33,9 @@ export class TransactionHistoryController extends Controller {
requestBody: EndpointTypes[typeof route]['input'],
@Res()
errorResponse: TsoaResponse<
StatusCodes.CONFLICT | StatusCodes.BAD_REQUEST | StatusCodes.UNPROCESSABLE_ENTITY,
| StatusCodes.CONFLICT
| StatusCodes.BAD_REQUEST
| StatusCodes.UNPROCESSABLE_ENTITY,
ErrorShape
>
): Promise<EndpointTypes[typeof route]['response']> {
Expand Down Expand Up @@ -62,7 +65,7 @@ export class TransactionHistoryController extends Controller {
const cardanoTxs = await tx<
ErrorShape | [TransactionHistoryResponse, TransactionHistoryResponse]
>(pool, async dbTx => {
const [until, pageStart] = await Promise.all([
const [until, pageStart, slotBounds] = await Promise.all([
resolveUntilTransaction({
block_hash: Buffer.from(requestBody.untilBlock, 'hex'),
dbTx,
Expand All @@ -74,6 +77,12 @@ export class TransactionHistoryController extends Controller {
after_tx: Buffer.from(requestBody.after.tx, 'hex'),
dbTx,
}),
!requestBody.slotLimits
? Promise.resolve(undefined)
: slotBoundsPagination.run(
{ low: requestBody.slotLimits.from, high: requestBody.slotLimits.to },
dbTx
),
]);
if (until == null) {
return genErrorMessage(Errors.BlockHashNotFound, {
Expand All @@ -87,11 +96,38 @@ export class TransactionHistoryController extends Controller {
});
}

let pageStartWithSlot = pageStart;

// if the slotLimits field is set, this shrinks the tx id range
// accordingly if necessary.
if (requestBody.slotLimits) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We really should have comments in the code for things like this otherwise it makes it takes a while to understand what is going on

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added some comments already, not sure if now it's clear enough.

const bounds = slotBounds ? slotBounds[0] : { min_tx_id: -1, max_tx_id: -2 };

const minTxId = Number(bounds.min_tx_id);

if (!pageStartWithSlot) {
pageStartWithSlot = {
// block_id is not really used by this query.
block_id: -1,
// if no *after* argument is provided, this starts the pagination
// from the corresponding slot. This allows skipping slots you are
// not interested in. If there is also no slotLimits specified this
// starts from the first tx because of the default of -1.
tx_id: minTxId,
SebastienGllmt marked this conversation as resolved.
Show resolved Hide resolved
};
} else {
pageStartWithSlot.tx_id = Math.max(Number(bounds.min_tx_id), pageStartWithSlot.tx_id);
}

until.tx_id = Math.min(until.tx_id, Number(bounds.max_tx_id));
}

const commonRequest = {
after: pageStart,
after: pageStartWithSlot,
limit: requestBody.limit ?? ADDRESS_LIMIT.RESPONSE,
until,
dbTx,
withInputContext: !!requestBody.withInputContext
};
const result = await Promise.all([
historyForCredentials({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ISqlBlockLatestResult {
id: number;
payload: Buffer | null;
slot: number;
tx_count: number | null;
}

/** 'SqlBlockLatest' query type */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/** Types generated for queries found in "app/models/pagination/slotBoundsPagination.sql" */
import { PreparedQuery } from '@pgtyped/runtime';

/** 'SlotBoundsPagination' parameters type */
export interface ISlotBoundsPaginationParams {
high: number;
low: number;
}

/** 'SlotBoundsPagination' return type */
export interface ISlotBoundsPaginationResult {
max_tx_id: string | null;
min_tx_id: string | null;
}

/** 'SlotBoundsPagination' query type */
export interface ISlotBoundsPaginationQuery {
params: ISlotBoundsPaginationParams;
result: ISlotBoundsPaginationResult;
}

const slotBoundsPaginationIR: any = {"usedParamSet":{"low":true,"high":true},"params":[{"name":"low","required":true,"transform":{"type":"scalar"},"locs":[{"a":782,"b":786}]},{"name":"high","required":true,"transform":{"type":"scalar"},"locs":[{"a":1036,"b":1041}]}],"statement":"WITH\n low_block AS (\n SELECT\n \"Block\".id,\n \"Block\".slot\n FROM\n \"Block\"\n WHERE\n \n slot <= :low! AND tx_count > 0\n ORDER BY\n \"Block\".id DESC\n LIMIT\n 1\n ),\n high_block AS (\n SELECT\n \"Block\".id,\n \"Block\".slot\n FROM\n \"Block\"\n WHERE\n slot <= :high! AND tx_count > 0\n ORDER BY\n \"Block\".id DESC\n LIMIT 1\n ),\n min_hash AS (\n (SELECT\n COALESCE(MAX(\"Transaction\".id), -1) AS min_tx_id\n FROM\n \"Transaction\"\n JOIN low_block ON \"Transaction\".block_id = low_block.id\n GROUP BY\n low_block.slot\n LIMIT\n 1\n )\n UNION (SELECT min_tx_id FROM (values(-1)) s(min_tx_id))\n ORDER BY min_tx_id DESC\n LIMIT\n 1\n ),\n max_hash AS (\n SELECT\n COALESCE(MAX(\"Transaction\".id), -2) AS max_tx_id\n FROM\n \"Transaction\"\n JOIN high_block ON \"Transaction\".block_id = high_block.id\n GROUP BY\n high_block.slot\n )\nSELECT\n *\nFROM min_hash\nLEFT JOIN max_hash ON 1 = 1"};

/**
* Query generated from SQL:
* ```
* WITH
* low_block AS (
* SELECT
* "Block".id,
* "Block".slot
* FROM
* "Block"
* WHERE
*
* slot <= :low! AND tx_count > 0
* ORDER BY
* "Block".id DESC
* LIMIT
* 1
* ),
* high_block AS (
* SELECT
* "Block".id,
* "Block".slot
* FROM
* "Block"
* WHERE
* slot <= :high! AND tx_count > 0
* ORDER BY
* "Block".id DESC
* LIMIT 1
* ),
* min_hash AS (
* (SELECT
* COALESCE(MAX("Transaction".id), -1) AS min_tx_id
* FROM
* "Transaction"
* JOIN low_block ON "Transaction".block_id = low_block.id
* GROUP BY
* low_block.slot
* LIMIT
* 1
* )
* UNION (SELECT min_tx_id FROM (values(-1)) s(min_tx_id))
* ORDER BY min_tx_id DESC
* LIMIT
* 1
* ),
* max_hash AS (
* SELECT
* COALESCE(MAX("Transaction".id), -2) AS max_tx_id
* FROM
* "Transaction"
* JOIN high_block ON "Transaction".block_id = high_block.id
* GROUP BY
* high_block.slot
* )
* SELECT
* *
* FROM min_hash
* LEFT JOIN max_hash ON 1 = 1
* ```
*/
export const slotBoundsPagination = new PreparedQuery<ISlotBoundsPaginationParams,ISlotBoundsPaginationResult>(slotBoundsPaginationIR);


Loading
Loading