Skip to content

Commit 6527c10

Browse files
committed
add chain simulator e2e tests
1 parent aaacda7 commit 6527c10

7 files changed

Lines changed: 190 additions & 2 deletions

File tree

src/crons/websocket/websocket.cron.service.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Stats } from 'src/endpoints/network/entities/stats';
2020
import { TransactionsCustomGateway } from './transaction.custom.gateway';
2121
import { ConnectionHandler } from './connection.handler';
2222
import { EventsCustomGateway } from './events.custom.gateway';
23+
import { ApiConfigService } from 'src/common/api-config/api.config.service';
2324

2425
@Injectable()
2526
@WebSocketGateway({ cors: { origin: '*' }, path: '/ws/subscription' })
@@ -41,6 +42,7 @@ export class WebsocketCronService {
4142
private readonly transactionsCustomGateway: TransactionsCustomGateway,
4243
private readonly eventsCustomGateway: EventsCustomGateway,
4344
private readonly connectionHandler: ConnectionHandler,
45+
private readonly apiConfigService: ApiConfigService,
4446
) { }
4547

4648
@Cron('*/1 * * * * *')
@@ -132,10 +134,18 @@ export class WebsocketCronService {
132134
}
133135

134136
private async isElasticDataAvailableForTimestampMs(timestampMs: number, networkStats: Stats) {
135-
const nextRoundTimestampMs = timestampMs + networkStats.refreshRate;
137+
let nextRoundTimestampMs = timestampMs + networkStats.refreshRate;
138+
let searchField = 'timestampMs';
139+
140+
// todo: remove after timestampMs is supported on chain simulator
141+
if (this.apiConfigService.getNetwork() === 'chain') {
142+
searchField = 'timestamp';
143+
nextRoundTimestampMs = Math.ceil(nextRoundTimestampMs / 1000);
144+
}
145+
136146
const rounds = await this.elasticService.getCount(
137147
'rounds',
138-
ElasticQuery.create().withMustCondition(QueryType.Match('timestampMs', nextRoundTimestampMs))
148+
ElasticQuery.create().withMustCondition(QueryType.Match(searchField, nextRoundTimestampMs))
139149
);
140150

141151
return rounds === networkStats.shards + 1; // +1 for metachain

src/crons/websocket/websocket.subscription.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ConnectionHandler } from './connection.handler';
1515
import { RoundModule } from 'src/endpoints/rounds/round.module';
1616
import { TransactionsCustomGateway } from './transaction.custom.gateway';
1717
import { EventsCustomGateway } from './events.custom.gateway';
18+
import { ApiConfigModule } from 'src/common/api-config/api.config.module';
1819

1920
@Module({
2021
imports: [
@@ -25,6 +26,7 @@ import { EventsCustomGateway } from './events.custom.gateway';
2526
PoolModule,
2627
EventsModule,
2728
RoundModule,
29+
ApiConfigModule,
2830
],
2931
providers: [
3032
WebsocketCronService,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
CHAIN_SIMULATOR_URL=http://localhost:8085
22
API_SERVICE_URL=http://localhost:3001
3+
SUBSCRIPTIONS_SERIVCE_URL=http://localhost:6002
34
ALICE_ADDRESS=erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th
45
BOB_ADDRESS=erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx

src/test/chain-simulator/config/env.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ dotenv.config({
88
export const config = {
99
chainSimulatorUrl: process.env.CHAIN_SIMULATOR_URL || 'http://localhost:8085',
1010
apiServiceUrl: process.env.API_SERVICE_URL || 'http://localhost:3001',
11+
subscriptionsServiceUrl: process.env.SUBSCRIPTIONS_SERVICE_URL || 'http://localhost:6002',
1112
aliceAddress: process.env.ALICE_ADDRESS || 'erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th',
1213
bobAddress: process.env.BOB_ADDRESS || 'erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx',
1314
};

src/test/chain-simulator/docker/overridable-config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ OverridableConfigTomlValues = [
55
{ File = "systemSmartContractsConfig.toml", Path = "ESDTSystemSCConfig.BaseIssuingCost", Value = "50000000000000000" }, # (0.05 EGLD)
66
{ File = "config.toml", Path = "Debug.Process.Enabled", Value = false },
77
{ File = "config.toml", Path = "WebServerAntiflood.WebServerAntifloodEnabled", Value = false}
8+
{ File = "config.toml", Path = "network", Value = "chain"}
89
]

src/test/chain-simulator/utils/chain.simulator.operations.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,3 +623,30 @@ export async function transferNftFromTo(
623623
console.log(`NFT transfer completed. Transaction hash: ${txHash}`);
624624
return txHash;
625625
}
626+
627+
export async function transferEgld(
628+
chainSimulatorUrl: string,
629+
senderAddress: string,
630+
receiverAddress: string,
631+
amountInEgldNominated: number
632+
): Promise<string> {
633+
const amountInEgldNominatedStr = amountInEgldNominated.toString();
634+
const egldDecimals = '0'.repeat(18);
635+
console.log(`Transferring ${amountInEgldNominated} EGLD from ${senderAddress} to ${receiverAddress}`);
636+
637+
const txHash = await sendTransaction(
638+
new SendTransactionArgs({
639+
chainSimulatorUrl,
640+
sender: senderAddress,
641+
receiver: receiverAddress,
642+
value: (amountInEgldNominatedStr.concat(egldDecimals)),
643+
dataField: '',
644+
}),
645+
);
646+
647+
console.log(`EGLD transfer completed. Transaction hash: ${txHash}`);
648+
await axios.post(
649+
`${chainSimulatorUrl}/simulator/generate-blocks-until-transaction-processed/${txHash}`,
650+
);
651+
return txHash;
652+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import axios from "axios";
2+
import { config } from "./config/env.config";
3+
import { fundAddress, transferEgld } from "./utils/chain.simulator.operations";
4+
import { io, Socket } from "socket.io-client";
5+
6+
const WS_SERVER_URL = `${config.subscriptionsServiceUrl}`;
7+
8+
const subscriptionsResponses: Map<string, any[]> = new Map();
9+
10+
const filters = {
11+
CLIENT_1: { sender: config.aliceAddress },
12+
CLIENT_2: { sender: config.bobAddress },
13+
CLIENT_3: { sender: config.aliceAddress, receiver: config.bobAddress }
14+
};
15+
16+
const filterKeys = {
17+
CLIENT_1: JSON.stringify(filters.CLIENT_1),
18+
CLIENT_2: JSON.stringify(filters.CLIENT_2),
19+
CLIENT_3: JSON.stringify(filters.CLIENT_3)
20+
}
21+
22+
const filterMap = [
23+
{ key: filterKeys.CLIENT_1, filter: filters.CLIENT_1, clientId: "client1" },
24+
{ key: filterKeys.CLIENT_2, filter: filters.CLIENT_2, clientId: "client2" },
25+
{ key: filterKeys.CLIENT_3, filter: filters.CLIENT_3, clientId: "client3" }
26+
];
27+
28+
29+
describe('Websocket subscriptions e2e tests with chain simulator', () => {
30+
jest.setTimeout(100000);
31+
32+
const clients: Socket[] = [];
33+
34+
const connectAndSubscribe = (filterKey: string, filter: any, clientId: string) => {
35+
const clientLabel = clientId;
36+
const receivedTxs: any[] = [];
37+
38+
subscriptionsResponses.set(filterKey, receivedTxs);
39+
40+
const client: Socket = io(WS_SERVER_URL, {
41+
path: '/ws/subscription'
42+
});
43+
clients.push(client);
44+
45+
46+
47+
client.on("connect_error", (err) => {
48+
throw new Error(`${clientLabel} connection failed: ${err.message}`);
49+
});
50+
51+
client.on("error", (err) => {
52+
throw new Error(`Error for ${clientLabel}: ${err.message}`);
53+
});
54+
55+
client.on("customTransactionUpdate", (data: { transactions: any[] }) => {
56+
console.log(`\n💸 ${clientLabel} received ${data.transactions.length} txs`);
57+
receivedTxs.push(...data.transactions);
58+
});
59+
60+
client.on("connect", () => {
61+
console.log(`\n ${clientLabel} subscribing to TXs:`, JSON.stringify(filter));
62+
63+
client.emit("subscribeCustomTransactions", filter, (ack: any) => {
64+
console.log('ACK Response:', ack);
65+
});
66+
});
67+
68+
};
69+
70+
beforeAll(async () => {
71+
console.log("--- Executing beforeAll (Setup) ---");
72+
73+
try {
74+
// 1. Setup Chain Simulator
75+
await fundAddress(config.chainSimulatorUrl, config.aliceAddress);
76+
await fundAddress(config.chainSimulatorUrl, config.bobAddress);
77+
await axios.post(`${config.chainSimulatorUrl}/simulator/generate-blocks/1`);
78+
79+
for (const item of filterMap) {
80+
connectAndSubscribe(item.key, item.filter, item.clientId);
81+
}
82+
83+
// await for clients to connect
84+
console.log(`Awaiting for clients to connect...`)
85+
await new Promise(resolve => setTimeout(resolve, 5000));
86+
87+
console.log("\n--- Starting Transactions ---");
88+
89+
await transferEgld(config.chainSimulatorUrl, config.aliceAddress, config.bobAddress, 1);
90+
await transferEgld(config.chainSimulatorUrl, config.bobAddress, config.aliceAddress, 2);
91+
92+
console.log("--- Generating Block and waiting for WS responses ---");
93+
await axios.post(`${config.chainSimulatorUrl}/simulator/generate-blocks/10`);
94+
95+
await new Promise(resolve => setTimeout(resolve, 15000));
96+
97+
console.log("--- Setup Complete ---");
98+
99+
} catch (e: any) {
100+
console.error("An error occured in beforeAll:", e.message);
101+
102+
throw e;
103+
}
104+
});
105+
106+
afterAll(() => {
107+
clients.forEach(client => client.connected && client.disconnect());
108+
console.log("\n--- All clients disconnected ---");
109+
});
110+
111+
// --- TESTE SEPARATE (itShould...) ---
112+
113+
it('should receive only the transaction sent by Alice (Tx 1: Alice -> Bob) when filtering by CLIENT_1', () => {
114+
const filterKey = filterKeys.CLIENT_1;
115+
const aliceTxs = subscriptionsResponses.get(filterKey);
116+
console.log(`\nRunning test for ${filterMap.find(f => f.key === filterKey)?.clientId}`);
117+
118+
expect(aliceTxs?.length).toBe(1);
119+
const tx = aliceTxs?.[0];
120+
expect(tx.sender).toEqual(config.aliceAddress);
121+
expect(tx.sender).not.toEqual(config.bobAddress);
122+
});
123+
124+
it('should receive only the transaction sent by Bob (Tx 2: Bob -> Alice) when filtering by CLIENT_2', () => {
125+
const filterKey = filterKeys.CLIENT_2;
126+
const bobTxs = subscriptionsResponses.get(filterKey);
127+
console.log(`\nRunning test for ${filterMap.find(f => f.key === filterKey)?.clientId}`);
128+
129+
expect(bobTxs?.length).toBe(1);
130+
const tx = bobTxs?.[0];
131+
expect(tx.sender).toEqual(config.bobAddress);
132+
expect(tx.receiver).toEqual(config.aliceAddress);
133+
expect(tx.sender).not.toEqual(config.aliceAddress);
134+
});
135+
136+
it('should receive only the transaction sent by Alice to Bob (Tx 1) when filtering by CLIENT_3', () => {
137+
const filterKey = filterKeys.CLIENT_3;
138+
const aliceToBobTxs = subscriptionsResponses.get(filterKey);
139+
console.log(`\nRunning test for ${filterMap.find(f => f.key === filterKey)?.clientId}`);
140+
141+
expect(aliceToBobTxs?.length).toBe(1);
142+
const tx = aliceToBobTxs?.[0];
143+
expect(tx.sender).toEqual(config.aliceAddress);
144+
expect(tx.receiver).toEqual(config.bobAddress);
145+
});
146+
});

0 commit comments

Comments
 (0)