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
175 changes: 98 additions & 77 deletions packages/daemon/__tests__/integration/balances.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import * as Services from '../../src/services';
import { SyncMachine } from '../../src/machines';
import { interpret } from 'xstate';
import { getLastSyncedEvent, getDbConnection } from '../../src/db';
import { getDbConnection } from '../../src/db';
import { Connection } from 'mysql2/promise';
import { cleanDatabase, fetchAddressBalances, validateBalances } from './utils';
import { cleanDatabase, fetchAddressBalances, transitionUntilEvent, validateBalances } from './utils';
import unvoidedScenarioBalances from './scenario_configs/unvoided_transactions.balances';
import reorgScenarioBalances from './scenario_configs/reorg.balances';
import singleChainBlocksAndTransactionsBalances from './scenario_configs/single_chain_blocks_and_transactions.balances';
import invalidMempoolBalances from './scenario_configs/invalid_mempool_transaction.balances';
import emptyScriptBalances from './scenario_configs/empty_script.balances';
import customScriptBalances from './scenario_configs/custom_script.balances';

import {
DB_NAME,
DB_USER,
Expand All @@ -28,6 +32,10 @@ import {
SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS_PORT,
SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS_LAST_EVENT,
INVALID_MEMPOOL_TRANSACTION_LAST_EVENT,
CUSTOM_SCRIPT_PORT,
CUSTOM_SCRIPT_LAST_EVENT,
EMPTY_SCRIPT_PORT,
EMPTY_SCRIPT_LAST_EVENT,
} from './config';

jest.mock('../../src/config', () => {
Expand Down Expand Up @@ -104,25 +112,11 @@ describe('unvoided transaction scenario', () => {

const machine = interpret(SyncMachine);

await new Promise<void>((resolve) => {
machine.onTransition(async (state) => {
if (state.matches('CONNECTED.idle')) {
// @ts-ignore
const lastSyncedEvent = await getLastSyncedEvent(mysql);
if (lastSyncedEvent?.last_event_id === UNVOIDED_SCENARIO_LAST_EVENT) {
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, unvoidedScenarioBalances));

machine.stop();

resolve();
}
}
});

machine.start();
});
// @ts-ignore
await transitionUntilEvent(mysql, machine, UNVOIDED_SCENARIO_LAST_EVENT);
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, unvoidedScenarioBalances));
});
});

Expand Down Expand Up @@ -153,25 +147,11 @@ describe('reorg scenario', () => {

const machine = interpret(SyncMachine);

await new Promise<void>((resolve) => {
machine.onTransition(async (state) => {
if (state.matches('CONNECTED.idle')) {
// @ts-ignore
const lastSyncedEvent = await getLastSyncedEvent(mysql);
if (lastSyncedEvent?.last_event_id === REORG_SCENARIO_LAST_EVENT) {
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, reorgScenarioBalances));

machine.stop();

resolve();
}
}
});

machine.start();
});
// @ts-ignore
await transitionUntilEvent(mysql, machine, REORG_SCENARIO_LAST_EVENT);
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, reorgScenarioBalances));
});
});

Expand Down Expand Up @@ -202,29 +182,50 @@ describe('single chain blocks and transactions scenario', () => {

const machine = interpret(SyncMachine);

await new Promise<void>((resolve) => {
machine.onTransition(async (state) => {
if (state.matches('CONNECTED.idle')) {
// @ts-ignore
const lastSyncedEvent = await getLastSyncedEvent(mysql);
if (lastSyncedEvent?.last_event_id === SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS_LAST_EVENT) {
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, singleChainBlocksAndTransactionsBalances));

machine.stop();
// @ts-ignore
await transitionUntilEvent(mysql, machine, SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS_LAST_EVENT);
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, singleChainBlocksAndTransactionsBalances));
});
});

resolve();
}
}
});
describe('invalid mempool transactions scenario', () => {
beforeAll(() => {
jest.spyOn(Services, 'fetchMinRewardBlocks').mockImplementation(async () => 300);
});

machine.start();
it('should do a full sync and the balances should match', async () => {
// @ts-ignore
getConfig.mockReturnValue({
NETWORK: 'testnet',
SERVICE_NAME: 'daemon-test',
CONSOLE_LEVEL: 'debug',
TX_CACHE_SIZE: 100,
BLOCK_REWARD_LOCK: 300,
FULLNODE_PEER_ID: 'simulator_peer_id',
STREAM_ID: 'simulator_stream_id',
FULLNODE_NETWORK: 'simulator_network',
FULLNODE_HOST: `127.0.0.1:${INVALID_MEMPOOL_TRANSACTION_PORT}`,
USE_SSL: false,
DB_ENDPOINT,
DB_NAME,
DB_USER,
DB_PASS,
DB_PORT,
});

const machine = interpret(SyncMachine);

// @ts-ignore
await transitionUntilEvent(mysql, machine, INVALID_MEMPOOL_TRANSACTION_LAST_EVENT);
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, invalidMempoolBalances));
});
});

describe('invalid mempool transactions scenario', () => {
describe('custom script scenario', () => {
beforeAll(() => {
jest.spyOn(Services, 'fetchMinRewardBlocks').mockImplementation(async () => 300);
});
Expand All @@ -240,7 +241,7 @@ describe('invalid mempool transactions scenario', () => {
FULLNODE_PEER_ID: 'simulator_peer_id',
STREAM_ID: 'simulator_stream_id',
FULLNODE_NETWORK: 'simulator_network',
FULLNODE_HOST: `127.0.0.1:${INVALID_MEMPOOL_TRANSACTION_PORT}`,
FULLNODE_HOST: `127.0.0.1:${CUSTOM_SCRIPT_PORT}`,
USE_SSL: false,
DB_ENDPOINT,
DB_NAME,
Expand All @@ -251,25 +252,45 @@ describe('invalid mempool transactions scenario', () => {

const machine = interpret(SyncMachine);

await new Promise<void>((resolve) => {
machine.onTransition(async (state) => {
const addressBalances = await fetchAddressBalances(mysql);
if (state.matches('CONNECTED.idle')) {
// @ts-ignore
const lastSyncedEvent = await getLastSyncedEvent(mysql);
console.log(lastSyncedEvent);
if (lastSyncedEvent?.last_event_id === INVALID_MEMPOOL_TRANSACTION_LAST_EVENT) {
// @ts-ignore
expect(validateBalances(addressBalances, invalidMempoolBalances));

machine.stop();

resolve();
}
}
});

machine.start();
// @ts-ignore
await transitionUntilEvent(mysql, machine, CUSTOM_SCRIPT_LAST_EVENT);
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, customScriptBalances));
});
});

describe('empty script scenario', () => {
beforeAll(() => {
jest.spyOn(Services, 'fetchMinRewardBlocks').mockImplementation(async () => 300);
});

it('should do a full sync and the balances should match', async () => {
// @ts-ignore
getConfig.mockReturnValue({
NETWORK: 'testnet',
SERVICE_NAME: 'daemon-test',
CONSOLE_LEVEL: 'debug',
TX_CACHE_SIZE: 100,
BLOCK_REWARD_LOCK: 300,
FULLNODE_PEER_ID: 'simulator_peer_id',
STREAM_ID: 'simulator_stream_id',
FULLNODE_NETWORK: 'simulator_network',
FULLNODE_HOST: `127.0.0.1:${EMPTY_SCRIPT_PORT}`,
USE_SSL: false,
DB_ENDPOINT,
DB_NAME,
DB_USER,
DB_PASS,
DB_PORT,
});

const machine = interpret(SyncMachine);

// @ts-ignore
await transitionUntilEvent(mysql, machine, EMPTY_SCRIPT_LAST_EVENT);
const addressBalances = await fetchAddressBalances(mysql);
// @ts-ignore
expect(validateBalances(addressBalances, emptyScriptBalances));
});
});
15 changes: 14 additions & 1 deletion packages/daemon/__tests__/integration/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,17 @@ export const SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS_LAST_EVENT = 37;
export const INVALID_MEMPOOL_TRANSACTION_PORT = 8085;
export const INVALID_MEMPOOL_TRANSACTION_LAST_EVENT = 40;

export const SCENARIOS = ['UNVOIDED_SCENARIO', 'REORG_SCENARIO', 'SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS', 'INVALID_MEMPOOL_TRANSACTION'];
export const CUSTOM_SCRIPT_PORT = 8086;
export const CUSTOM_SCRIPT_LAST_EVENT = 37;

export const EMPTY_SCRIPT_PORT = 8087;
export const EMPTY_SCRIPT_LAST_EVENT = 37;

export const SCENARIOS = [
'UNVOIDED_SCENARIO',
'REORG_SCENARIO',
'SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS',
'INVALID_MEMPOOL_TRANSACTION',
'EMPTY_SCRIPT',
'CUSTOM_SCRIPT',
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default {
"HVayMofEDh4XGsaQJeRJKhutYxYodYNop6": 100000000000,
"HJQbEERnD5Ak3f2dsi8zAmsZrCWTT8FZns": 6400,
"HRSYchTEsFFpZAkgSTMsohNGQ6eLPyhXvJ": 6400,
"HQfVqxyxQV4BHwnsMnRXpZGmwPYiNSVmMu": 6400,
"HPnkpR2vnBuCoZCEnRZNHMBtf8ygeSidbW": 6400,
"HPNvtPZaDF44i6CL91u4BvZPu6z2xPNt26": 6400,
"HQijr325t63VJFdc4vYkaTyd87oeBLpSed": 6400,
"H8fCNrYGkj4B6VzKgtRiHBgoSxM31d65JR": 6400,
"HAqrADnn7GyyT68fSX8zmtsRNFyabPzoRQ": 6400,
"HRTH6uGo7zn3LWrosBYn7eXkwAeHAHTRh8": 6400,
"HJPSMHCFv2dRb78wZPMsAzwLQHSkBpfuLn": 6400,
"HFtz2f59Lms4p3Jfgtsr73s97MbJHsRENh": 1000,
"H9hHteu9QdAS5p6X743Mpfue6G19rV9GeY": 5400,
"HSd6PqXesUmHHv6MoN24aUiMuw7Pdcxrwk": 6400
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default {
"HVayMofEDh4XGsaQJeRJKhutYxYodYNop6": 100000000000,
"HFtz2f59Lms4p3Jfgtsr73s97MbJHsRENh": 6400,
"HJQbEERnD5Ak3f2dsi8zAmsZrCWTT8FZns": 6400,
"HRSYchTEsFFpZAkgSTMsohNGQ6eLPyhXvJ": 6400,
"HQfVqxyxQV4BHwnsMnRXpZGmwPYiNSVmMu": 6400,
"HPnkpR2vnBuCoZCEnRZNHMBtf8ygeSidbW": 6400,
"HPNvtPZaDF44i6CL91u4BvZPu6z2xPNt26": 6400,
"HQijr325t63VJFdc4vYkaTyd87oeBLpSed": 6400,
"H8fCNrYGkj4B6VzKgtRiHBgoSxM31d65JR": 6400,
"HAqrADnn7GyyT68fSX8zmtsRNFyabPzoRQ": 6400,
"HRTH6uGo7zn3LWrosBYn7eXkwAeHAHTRh8": 6400,
"HRQe4CXj8AZXzSmuNztU8iQR74QTQMbnTs": 1000,
"HNqTfEASfdx7H4vMUGzfD2HyD3GeKuxjTJ": 5400,
"HRH8Wbmr1A3BrLswSBhvVE4hhsv4jUdyVA": 6400
};
23 changes: 21 additions & 2 deletions packages/daemon/__tests__/integration/scripts/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3.9"

services:
mysql:
image: mysql
Expand All @@ -9,6 +7,7 @@ services:
MYSQL_ROOT_PASSWORD: hathor
ports:
- "3380:3306"

unvoided_transaction:
image: hathornetwork/hathor-core:stable
command: [
Expand Down Expand Up @@ -46,5 +45,25 @@ services:
ports:
- "8085:8080"

custom_scripts:
image: hathornetwork/hathor-core:stable
command: [
"events_simulator",
"--scenario", "CUSTOM_SCRIPT",
"--seed", "1"
]
ports:
- "8086:8080"

empty_script:
image: hathornetwork/hathor-core:stable
command: [
"events_simulator",
"--scenario", "EMPTY_SCRIPT",
"--seed", "1"
]
ports:
- "8087:8080"

networks:
database:
24 changes: 21 additions & 3 deletions packages/daemon/__tests__/integration/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import { Connection } from 'mysql2/promise';
import { AddressBalance, AddressBalanceRow } from '../../../src/types';
import { Interpreter } from 'xstate';
import { getLastSyncedEvent } from '../../../src/db';
import { AddressBalance, AddressBalanceRow, Context, Event } from '../../../src/types';

export const cleanDatabase = async (mysql: Connection): Promise<void> => {
const TABLES = [
Expand Down Expand Up @@ -68,9 +70,25 @@ export const validateBalances = async (
const totalBalanceA = balanceA.lockedBalance + balanceA.unlockedBalance;

if (totalBalanceA !== balanceB) {
console.log(totalBalanceA);
console.log(balanceB);
throw new Error(`Balances are not equal for address: ${address}, expected: ${balanceB}, received: ${totalBalanceA}`);
}
}
};

export async function transitionUntilEvent(mysql: Connection, machine: Interpreter<Context, any, Event>, eventId: number) {
return await new Promise<void>((resolve) => {
machine.onTransition(async (state) => {
if (state.matches('CONNECTED.idle')) {
// @ts-ignore
const lastSyncedEvent = await getLastSyncedEvent(mysql);
if (lastSyncedEvent?.last_event_id === eventId) {
machine.stop();

resolve();
}
}
});

machine.start();
});
}
8 changes: 5 additions & 3 deletions packages/daemon/src/utils/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ export const getAddressBalanceMap = (

for (const input of inputs) {
if (!input.decoded) {
throw new Error('Input has no decoded script');
// If we're unable to decode the script, we will also be unable to
// calculate the balance, so just skip this input.
continue;
}

const address = input.decoded?.address;
Expand Down Expand Up @@ -294,11 +296,11 @@ export const prepareInputs = (inputs: EventTxInput[], tokens: string[]): TxInput
// @ts-ignore
script: utxo.script,
token,
decoded: {
decoded: output.decoded ? {
type: output.decoded.type,
address: output.decoded.address,
timelock: output.decoded.timelock,
},
Comment on lines +299 to -301
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.

Is this decoded from the fullnode or local?
A data output has a valid decoded with type and data but it does not have an address.

Copy link
Copy Markdown
Collaborator Author

@andreabadesso andreabadesso Oct 8, 2024

Choose a reason for hiding this comment

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

The fullnode sends it already decoded, exactly with this structure (including the address). This comes from the event queue

} : null,
};

return [...newInputs, input];
Expand Down