Skip to content
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
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
],
"devDependencies": {
"@size-limit/preset-small-lib": "^4.10.2",
"@types/lodash": "^4.14.171",
"husky": "^6.0.0",
"size-limit": "^4.10.2",
"tsdx": "^0.14.1",
Expand All @@ -57,6 +58,7 @@
"aws-sdk": "^2.878.0",
"axios": "^0.21.1",
"dotenv": "^8.2.0",
"lodash": "^4.17.21",
"websocket": "^1.0.33",
"winston": "^3.3.3",
"xstate": "^4.17.1"
Expand Down
16 changes: 10 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export interface Block {
}

export interface DecodedScript {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If every member of this type is optional, why not use Partial? We could also use DecodedScript | {} but then we would need to make type assertions where needed (from typescript issues).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Since this will need a refactor everywhere that uses DecodedScript, I will create an issue to refactor this and merge this PR as we discussed in Slack

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

#16

type: string;
address: string;
type?: string;
address?: string;
timelock?: number | undefined | null;
value?: number | undefined | null;
tokenData?: number | undefined | null;
Expand Down Expand Up @@ -222,12 +222,15 @@ export interface RawDecodedInput {
token_data: number;
}

/* Everything is optional because scripts that were not able to
* be decoded will be returned as {}
*/
export interface RawDecodedOutput {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Same as above

type: string;
address: string;
type?: string;
address?: string;
timelock?: number | null;
value: number;
token_data: number;
value?: number;
token_data?: number;
}

export interface RawInput {
Expand Down Expand Up @@ -276,6 +279,7 @@ export interface Meta {
accumulated_weight: number;
score: number;
height: number;
validation?: string;
Comment thread
pedroferreira1 marked this conversation as resolved.
first_block?: string | null;
}

Expand Down
40 changes: 31 additions & 9 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import dotenv from 'dotenv';
// @ts-ignore
import { wallet } from '@hathor/wallet-lib';
import logger from './logger';
import { isNumber, isNil, get } from 'lodash';

dotenv.config();

Expand Down Expand Up @@ -79,8 +80,8 @@ export const downloadTxFromId = async (txId: string): Promise<FullTx | null> =>

const txData: RawTxResponse = await downloadTx(txId);
const { tx, meta } = txData;
return parseTx(tx);

return parseTx(tx);
};

/**
Expand Down Expand Up @@ -137,9 +138,30 @@ export const recursivelyDownloadTx = async (blockId: string, txIds: string[] = [
!data.has(parent)
});

return recursivelyDownloadTx(blockId, [...txIds, ...newParents], data.set(parsedTx.txId, parsedTx));
const newData = data.set(parsedTx.txId, cleanInvalidOutputs(parsedTx));

return recursivelyDownloadTx(blockId, [...txIds, ...newParents], newData);
};

/**
* Removes invalid tx outputs from the received tx
*
* @param tx - `FullTx` to remove the invalid outputs
*/
export const cleanInvalidOutputs = (tx: FullTx) => ({
...tx,
// Filter outputs that we can't handle (script was unable to be decoded)
outputs: tx.outputs.filter((output, index) => {
const validDecoded = !isNil(get(output, 'decoded.type'));

if (!validDecoded) {
logger.warn(`Ignoring tx output with index ${index} from tx ${tx.txId} as script couldn't be decoded.`);
}

return validDecoded;
}),
});

/**
* Prepares a transaction to be sent to the wallet-service `onNewTxRequest`
*
Expand Down Expand Up @@ -237,9 +259,9 @@ export const parseTx = (tx: RawTx): FullTx => {
const typedDecodedScript: DecodedScript = {
type: input.decoded.type as string,
address: input.decoded.address as string,
timelock: input.decoded.timelock ? input.decoded.timelock as number : null,
value: input.decoded.value ? input.decoded.value as number : null,
tokenData: input.decoded.token_data ? input.decoded.token_data as number : null,
timelock: isNumber(input.decoded.timelock) ? input.decoded.timelock as number : null,
value: isNumber(input.decoded.value) ? input.decoded.value as number : null,
tokenData: isNumber(input.decoded.token_data) ? input.decoded.token_data as number : null,
};
const typedInput: Input = {
txId: input.tx_id as string,
Expand All @@ -256,9 +278,9 @@ export const parseTx = (tx: RawTx): FullTx => {
const typedDecodedScript: DecodedScript = {
type: output.decoded.type as string,
address: output.decoded.address as string,
timelock: output.decoded.timelock ? output.decoded.timelock as number : null,
value: output.decoded.value ? output.decoded.value as number : null,
tokenData: output.decoded.token_data ? output.decoded.token_data as number : null,
timelock: isNumber(output.decoded.timelock) ? output.decoded.timelock as number : null,
value: isNumber(output.decoded.value) ? output.decoded.value as number : null,
tokenData: isNumber(output.decoded.token_data) ? output.decoded.token_data as number : null,
};

const typedOutput: Output = {
Expand Down Expand Up @@ -311,7 +333,7 @@ export async function* syncLatestMempool(): AsyncGenerator<MempoolEvent> {
return;
}

const preparedTx: PreparedTx = prepareTx(tx);
const preparedTx: PreparedTx = prepareTx(cleanInvalidOutputs(tx));

try {
const sendTxResponse: ApiResponse = await sendTx(preparedTx);
Expand Down
13 changes: 13 additions & 0 deletions test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
BLOCK_BY_HEIGHT,
MOCK_TXS,
MOCK_FULL_TXS,
MOCK_NFT_TX,
MOCK_CREATE_TOKEN_TX,
generateBlock,
} from './utils';
Expand All @@ -24,6 +25,7 @@ import {
import {
prepareTx,
parseTx,
cleanInvalidOutputs,
} from '../src/utils';
import * as Utils from '../src/utils';
import * as FullNode from '../src/api/fullnode';
Expand Down Expand Up @@ -263,3 +265,14 @@ test('prepareTx on a CREATE_TOKEN tx should have token_name and token_symbol', a
expect(preparedTx.token_name).toStrictEqual('XCoin');
expect(preparedTx.token_symbol).toStrictEqual('XCN');
}, 500);

test('cleanInvalidOutputs on NFT transaction', async () => {
expect.hasAssertions();

const { tx } = MOCK_NFT_TX;
const parsedTx = parseTx(tx);

const cleanedTx = cleanInvalidOutputs(parsedTx);

expect(cleanedTx.outputs.length).toStrictEqual(2);
});
99 changes: 98 additions & 1 deletion test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface DecodedScript {
address: string,
timelock?: number,
}

export const MOCK_FULL_TXS: FullTx[] = [{
txId: '0000000033a3bb347e0401d85a70b38f0aa7b5e37ea4c70d7dacf8e493946e64',
nonce: '2553516830',
Expand Down Expand Up @@ -269,4 +270,100 @@ export const MOCK_CREATE_TOKEN_TX: RawTxResponse = {
first_block: "000000bd45ecc5119963cc3fa03e894f574e69811eef266ed7c6a0d4c1e1806c"
},
spent_outputs: {}
}
};

export const MOCK_NFT_TX: RawTxResponse = {
"success": true,
"tx": {
"hash": "0055c424b9038b0a8888b574ccdb1933a007fdfc15b91a4b38a48cc883b540bf",
"nonce": "389",
"timestamp": 1626187098,
"version": 2,
"weight": 8.0,
"parents": [
"0055b20066e8168ad8f05e82d66a34d19970cfb1861281735215cdd84744d842",
"00bb42880bd1183ce34df2185d1431f531a0a95af3556e368fa72e462edf7a9f"
],
"inputs": [{
"value": 2,
"token_data": 0,
"script": "dqkU8uf1ieRE8taN5bCNug5z5UHMO6eIrA==",
"decoded": {
"type": "P2PKH",
"address": "WkpQH9t4ue4LbTQKAEWssiXnYHC8CyMp7J",
"timelock": null,
"value": 2,
"token_data": 0
},
"tx_id": "0055b20066e8168ad8f05e82d66a34d19970cfb1861281735215cdd84744d842",
"index": 1
}],
"outputs": [{
"value": 1,
"token_data": 0,
"script": "TFFodHRwczovL2lwZnMuaW8vaXBmcy9RbWJIdEZrWWlGSG5XdEV6bm01RFFHTVNOSmdwTExXeDdRNlBxdHAxb0NiQlpwL21ldGFkYXRhLmpzb26s",
"decoded": {}
},
{
"value": 2,
"token_data": 129,
"script": "dqkUYpULlr3iJ6sZbP3YIfgL52fasneIrA==",
"decoded": {
"type": "P2PKH",
"address": "WXfHeaEtr3fS9ex42V5chr2jY7wb5tdcWD",
"timelock": null,
"value": 2,
"token_data": 129
}
},
{
"value": 1,
"token_data": 1,
"script": "dqkUYpULlr3iJ6sZbP3YIfgL52fasneIrA==",
"decoded": {
"type": "P2PKH",
"address": "WXfHeaEtr3fS9ex42V5chr2jY7wb5tdcWD",
"timelock": null,
"value": 1,
"token_data": 1
}
}
],
"tokens": [{
"uid": "0055c424b9038b0a8888b574ccdb1933a007fdfc15b91a4b38a48cc883b540bf",
"name": "Furia Special Edition",
"symbol": "DPL9"
}],
"token_name": "Furia Special Edition",
"token_symbol": "DPL9",
"raw": "000201030055b20066e8168ad8f05e82d66a34d19970cfb1861281735215cdd84744d8420100694630440220692c2a95bbb335729520bc1717d9b6da7361ebfc5fb500e6ac45ed4243b4912202201980930659406e06a0f0117a9eb01a29f06faaebf9e4ec58cc96941681043a2e21020377708f22ac1e829c9cfbfd891bb99a47f460bf45d71f4841db404cbefdcb93000000010000544c5168747470733a2f2f697066732e696f2f697066732f516d624874466b596946486e5774457a6e6d354451474d534e4a67704c4c577837513650717470316f4362425a702f6d657461646174612e6a736f6eac0000000281001976a91462950b96bde227ab196cfdd821f80be767dab27788ac0000000101001976a91462950b96bde227ab196cfdd821f80be767dab27788ac01154675726961205370656369616c2045646974696f6e0444504c39402000000000000060eda55a020055b20066e8168ad8f05e82d66a34d19970cfb1861281735215cdd84744d84200bb42880bd1183ce34df2185d1431f531a0a95af3556e368fa72e462edf7a9f00000185"
},
"meta": {
"hash": "0055c424b9038b0a8888b574ccdb1933a007fdfc15b91a4b38a48cc883b540bf",
"spent_outputs": [
[
0,
[]
],
[
1,
[]
],
[
2,
[]
]
],
"received_by": [],
"children": [],
"conflict_with": [],
"voided_by": [],
"twins": [],
"accumulated_weight": 8.0,
"score": 0,
"height": 0,
"first_block": "000000b17b22dd27fb1205a1f810a2c4d40de1e20af140e001529642c4b173a1",
"validation": "full"
},
"spent_outputs": {}
};