Skip to content

Commit

Permalink
Merge pull request #460 from input-output-hk/fix/wrong-tx-id-for-tx-i…
Browse files Browse the repository at this point in the history
…nputs

fix(cardano-services): wrong tx onput `txId` value
  • Loading branch information
rhyslbw committed Sep 28, 2022
2 parents 88284ea + eb6f1a0 commit 706ef85
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 1,023 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
RedeemerModel,
StakeCertModel,
TransactionDataMap,
TxInOutModel,
TxInput,
TxInputModel,
TxOutMultiAssetModel,
TxOutTokenMap,
TxOutput,
TxOutputModel,
TxTokenMap,
WithCertIndex,
Expand All @@ -25,8 +27,8 @@ import { hexStringToBuffer } from '@cardano-sdk/util';
import {
mapCertificate,
mapRedeemer,
mapTxIn,
mapTxOut,
mapTxInModel,
mapTxOutModel,
mapTxOutTokenMap,
mapTxTokenMap,
mapWithdrawal
Expand All @@ -43,17 +45,14 @@ export class ChainHistoryBuilder {
this.#logger = logger;
}

public async queryTransactionInputsByHashes(
hashes: Cardano.TransactionId[],
collateral = false
): Promise<Cardano.TxIn[]> {
public async queryTransactionInputsByHashes(hashes: Cardano.TransactionId[], collateral = false): Promise<TxInput[]> {
const byteHashes = hashes.map((hash) => hexStringToBuffer(hash.toString()));
this.#logger.debug(`About to find inputs (collateral: ${collateral}) for transactions:`, byteHashes);
const result: QueryResult<TxInOutModel> = await this.#db.query(
const result: QueryResult<TxInputModel> = await this.#db.query(
collateral ? Queries.findTxCollateralsByHashes : Queries.findTxInputsByHashes,
[byteHashes]
);
return result.rows.length > 0 ? result.rows.map(mapTxIn) : [];
return result.rows.length > 0 ? result.rows.map(mapTxInModel) : [];
}

public async queryMultiAssetsByTxOut(txOutIds: BigInt[]): Promise<TxOutTokenMap> {
Expand All @@ -62,15 +61,15 @@ export class ChainHistoryBuilder {
return mapTxOutTokenMap(result.rows);
}

public async queryTransactionOutputsByHashes(hashes: Cardano.TransactionId[]): Promise<TxOutputModel[]> {
public async queryTransactionOutputsByHashes(hashes: Cardano.TransactionId[]): Promise<TxOutput[]> {
const byteHashes = hashes.map((hash) => hexStringToBuffer(hash.toString()));
this.#logger.debug('About to find outputs for transactions:', byteHashes);
const result: QueryResult<TxInOutModel> = await this.#db.query(Queries.findTxOutputsByHashes, [byteHashes]);
const result: QueryResult<TxOutputModel> = await this.#db.query(Queries.findTxOutputsByHashes, [byteHashes]);
if (result.rows.length === 0) return [];

const txOutIds = result.rows.flatMap((txOut) => BigInt(txOut.id));
const multiAssets = await this.queryMultiAssetsByTxOut(txOutIds);
return result.rows.map((txOut) => mapTxOut(txOut, multiAssets.get(txOut.id)));
return result.rows.map((txOut) => mapTxOutModel(txOut, multiAssets.get(txOut.id)));
}

public async queryTxMintByHashes(hashes: Cardano.TransactionId[]): Promise<TxTokenMap> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Queries from './queries';
import { BlockModel, BlockOutputModel, TipModel, TxInOutModel, TxModel } from './types';
import { BlockModel, BlockOutputModel, TipModel, TxInputModel, TxModel, TxOutputModel } from './types';
import {
BlocksByIdsArgs,
Cardano,
Expand All @@ -13,7 +13,7 @@ import { Logger } from 'ts-log';
import { MetadataService } from '../../Metadata';
import { Pool, QueryResult } from 'pg';
import { hexStringToBuffer } from '@cardano-sdk/util';
import { mapBlock, mapTxAlonzo, mapTxIn, mapTxOut } from './mappers';
import { mapBlock, mapTxAlonzo, mapTxIn, mapTxInModel, mapTxOut, mapTxOutModel } from './mappers';
import orderBy from 'lodash/orderBy';
import uniq from 'lodash/uniq';

Expand All @@ -34,29 +34,32 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider implements ChainH
sinceBlock
}: TransactionsByAddressesArgs): Promise<Cardano.TxAlonzo[]> {
this.#logger.debug(`About to find transactions of addresses ${addresses} since block ${sinceBlock ?? 0}`);
const inputsResults: QueryResult<TxInOutModel> = await this.db.query(Queries.findTxInputsByAddresses, [
const inputsResults: QueryResult<TxInputModel> = await this.db.query(Queries.findTxInputsByAddresses, [
addresses,
sinceBlock ?? 0
]);
const outputsResults: QueryResult<TxInOutModel> = await this.db.query(Queries.findTxOutputsByAddresses, [
const outputsResults: QueryResult<TxOutputModel> = await this.db.query(Queries.findTxOutputsByAddresses, [
addresses,
sinceBlock ?? 0
]);

if (inputsResults.rows.length === 0 && outputsResults.rows.length === 0) return [];

const ids = uniq([
...inputsResults.rows.map(mapTxIn).flatMap((input) => input.txId),
...outputsResults.rows.map((output) => mapTxOut(output)).flatMap((output) => output.txId)
...inputsResults.rows.map(mapTxInModel).flatMap((input) => input.txInputId),
...outputsResults.rows.map((outputModel) => mapTxOutModel(outputModel)).flatMap((output) => output.txId)
]);

return this.transactionsByHashes({ ids });
}

public async transactionsByHashes({ ids }: TransactionsByIdsArgs): Promise<Cardano.TxAlonzo[]> {
const byteIds = ids.map((id) => hexStringToBuffer(id.toString()));
this.#logger.debug('About to find transactions with hashes:', byteIds);
const txResults: QueryResult<TxModel> = await this.db.query(Queries.findTransactionsByHashes, [byteIds]);

if (txResults.rows.length === 0) return [];

const [inputs, outputs, mints, withdrawals, redeemers, metadata, collaterals, certificates] = await Promise.all([
this.#builder.queryTransactionInputsByHashes(ids),
this.#builder.queryTransactionOutputsByHashes(ids),
Expand All @@ -67,20 +70,13 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider implements ChainH
this.#builder.queryTransactionInputsByHashes(ids, true),
this.#builder.queryCertificatesByHashes(ids)
]);

return txResults.rows.map((tx) => {
const txId = Cardano.TransactionId(tx.id.toString('hex'));
const txInputs = orderBy(
inputs.filter((input) => input.txId === txId),
['index']
);
const txOutputs = orderBy(
outputs.filter((output) => output.txId === txId),
['index']
);
const txCollaterals = orderBy(
collaterals.filter((col) => col.txId === txId),
['index']
);
const txInputs = orderBy(inputs.filter((input) => input.txInputId === txId).map(mapTxIn), ['index']);
const txCollaterals = orderBy(collaterals.filter((col) => col.txInputId === txId).map(mapTxIn), ['index']);
const txOutputs = orderBy(outputs.filter((output) => output.txId === txId).map(mapTxOut), ['index']);

return mapTxAlonzo(tx, {
certificates: certificates.get(txId),
collaterals: txCollaterals,
Expand Down Expand Up @@ -109,12 +105,14 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider implements ChainH
const outputResult: QueryResult<BlockOutputModel> = await this.db.query(Queries.findBlocksOutputByHashes, [
byteIds
]);

return blocksResult.rows.map((block) => {
const blockOutput = outputResult.rows.find((output) => output.hash === block.hash) ?? {
fees: '0',
hash: block.hash,
output: '0'
};

return mapBlock(block, blockOutput, tip);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import {
MultiAssetModel,
RedeemerModel,
TipModel,
TxInOutModel,
TxInput,
TxInputModel,
TxModel,
TxOutMultiAssetModel,
TxOutTokenMap,
TxOutput,
TxOutputModel,
TxTokenMap,
WithCertIndex,
Expand Down Expand Up @@ -57,13 +59,27 @@ export const mapTxOutTokenMap = (multiAssetModels: TxOutMultiAssetModel[]): TxOu
return txTokenMap;
};

export const mapTxIn = (txInModel: TxInOutModel): Cardano.TxIn => ({
export const mapTxIn = (txIn: TxInput): Cardano.TxIn => ({
address: txIn.address,
index: txIn.index,
txId: txIn.txSourceId
});

export const mapTxInModel = (txInModel: TxInputModel): TxInput => ({
address: Cardano.Address(txInModel.address),
id: txInModel.id,
index: txInModel.index,
txId: Cardano.TransactionId(txInModel.tx_id.toString('hex'))
txInputId: Cardano.TransactionId(txInModel.tx_input_id.toString('hex')),
txSourceId: Cardano.TransactionId(txInModel.tx_source_id.toString('hex'))
});

export const mapTxOut = (txOut: TxOutput): Cardano.TxOut => ({
address: txOut.address,
datum: txOut.datum,
value: txOut.value
});

export const mapTxOut = (txOutModel: TxInOutModel, assets?: Cardano.TokenMap): TxOutputModel => ({
export const mapTxOutModel = (txOutModel: TxOutputModel, assets?: Cardano.TokenMap): TxOutput => ({
address: Cardano.Address(txOutModel.address),
datum: txOutModel.datum ? Cardano.util.Hash32ByteBase16(txOutModel.datum.toString('hex')) : undefined,
index: txOutModel.index,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ const selectTxInput = (collateral?: boolean) => `
SELECT
tx_in.id AS id,
tx_out.address AS address,
tx_out.value AS coin_value,
tx_in.tx_out_index AS "index",
tx.hash AS tx_id
tx.hash AS tx_input_id,
source_tx.hash AS tx_source_id
FROM tx_out
JOIN ${collateral ? 'collateral_tx_in' : 'tx_in'} AS tx_in
ON tx_out.tx_id = tx_in.tx_out_id
JOIN tx ON tx.id = tx_in.tx_in_id
AND tx_in.tx_out_index = tx_out.index`;
AND tx_in.tx_out_index = tx_out.index
JOIN tx AS source_tx
ON tx_out.tx_id = source_tx.id`;

const selectTxOutput = `
SELECT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,23 @@ export interface TxModel {
block_slot_no: number;
}

export interface TxInOutModel {
export interface TxInputModel {
address: string;
id: string;
index: number;
tx_input_id: Buffer;
tx_source_id: Buffer;
}

export interface TxInput {
address: Cardano.Address;
id: string;
index: number;
txInputId: Cardano.TransactionId;
txSourceId: Cardano.TransactionId;
}

export interface TxOutputModel {
address: string;
coin_value: string;
datum?: Buffer | null;
Expand All @@ -53,7 +69,7 @@ export interface TxInOutModel {
tx_id: Buffer;
}

export interface TxOutputModel extends Cardano.TxOut {
export interface TxOutput extends Cardano.TxOut {
txId: Cardano.TransactionId;
index: number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ describe('ChainHistoryHttpService', () => {
const tx: Cardano.TxAlonzo = response[0];
expect(response.length).toEqual(1);
expect(response).toMatchSnapshot();
expect(tx.body.collaterals?.length).toBeGreaterThan(0);
expect(tx.body.collaterals?.length).toEqual(0);
});

it('has certificates', async () => {
Expand Down Expand Up @@ -337,7 +337,7 @@ describe('ChainHistoryHttpService', () => {
Cardano.Address('addr_test1wphyve8r76kvfr5yn6k0fcmq0mn2uf6c6mvtsrafmr7awcg0vnzpg')
];
const response = await provider.transactionsByAddresses({ addresses });
expect(response).toHaveLength(11);
expect(response).toHaveLength(8);
expect(response).toMatchSnapshot();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ describe('ChainHistoryBuilder', () => {
describe('queryTransactionInputsByHashes', () => {
test('query transaction inputs by tx hashes', async () => {
const result = await builder.queryTransactionInputsByHashes([
Cardano.TransactionId('cefd2fcf657e5e5d6c35975f4e052f427819391b153ebb16ad8aa107ba5a3819'),
Cardano.TransactionId('952dfa431223fd671c5e9e048e016f70fcebd9e41fcb726969415ff692736eeb')
Cardano.TransactionId('face165bd7aa8d0d661cf1ceaa4e35d7611be3b1c7997da378c547aa2464a4fd'),
Cardano.TransactionId('19251f57476d7af2777252270413c01383d9503110a68b4fde1a239c119c4f5d')
]);
expect(result).toHaveLength(2);
expect(result).toHaveLength(1);
expect(result).toMatchSnapshot();
});
test('query transaction inputs with empty array', async () => {
Expand All @@ -45,7 +45,7 @@ describe('ChainHistoryBuilder', () => {
],
true
);
expect(result).toHaveLength(2);
expect(result).toHaveLength(0);
expect(result).toMatchSnapshot();
});
test('query transaction collateral inputs when tx not found or does not have any ', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,32 +163,16 @@ Map {
exports[`ChainHistoryBuilder queryTransactionInputsByHashes query transaction inputs by tx hashes 1`] = `
Array [
Object {
"address": "addr_test1qr74u5d3d07x53pq37acghulu7wvjt794s53xp2fz343drhx2aghqc4747n2yhu0dht5xz0t5j6n0fju3rs8mfj83tzqly49jr",
"index": 0,
"txId": "952dfa431223fd671c5e9e048e016f70fcebd9e41fcb726969415ff692736eeb",
},
Object {
"address": "addr_test1qpvl4gf5r5h5w4yjrf58rt8slltem9tqfge36zqhsz5kgc8f2tc4ydn4zz9p0eymajqa4vj87he7yz6ssmh9x9h685hst9rgvk",
"address": "addr_test1qpcnmvyjmxmsm75f747u566gw7ewz4mesdw7yl278uf9r3f5l7d7dpx2ymfwlm3e56flupga8yamjr2kwdt7dw77ktyqqtx2r7",
"id": "2791722",
"index": 0,
"txId": "cefd2fcf657e5e5d6c35975f4e052f427819391b153ebb16ad8aa107ba5a3819",
"txInputId": "19251f57476d7af2777252270413c01383d9503110a68b4fde1a239c119c4f5d",
"txSourceId": "face165bd7aa8d0d661cf1ceaa4e35d7611be3b1c7997da378c547aa2464a4fd",
},
]
`;

exports[`ChainHistoryBuilder queryTransactionInputsByHashes with collateral true query transaction collateral inputs by tx hashes 1`] = `
Array [
Object {
"address": "addr_test1vpmwgcd7xuqr60ej3qnlfeyy5qhaudhmnmxey6str7v4rrcd3t2q4",
"index": 0,
"txId": "5acd6efb1b66299f1c5a2c4221af4bcaa4ba9929e8e6aa0e3f48707fa1796fc3",
},
Object {
"address": "addr_test1qqemvcq8wdt9cv0sxsghv497zjsjzyzkekpw4vlvea44kdgy720vc4ggn5u4y39cc2wctg2u6sgackadnl78lxqq3m9q47wq8e",
"index": 0,
"txId": "24e75c64a309fd8fb400933795b2522ca818cba80a3838c2ff14cec2cc8ffe4e",
},
]
`;
exports[`ChainHistoryBuilder queryTransactionInputsByHashes with collateral true query transaction collateral inputs by tx hashes 1`] = `Array []`;

exports[`ChainHistoryBuilder queryTransactionOutputsByHashes query transaction outputs by tx hashes 1`] = `
Array [
Expand Down

0 comments on commit 706ef85

Please sign in to comment.