Skip to content
Open
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 api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ COMMUNITY_WALLETS_URL="https://raw.githubusercontent.com/soaresa/test_data/refs/

# Path to the json file containing well known addresses info
KNOWN_ADDRESSES_URL="https://raw.githubusercontent.com/soaresa/test_data/refs/heads/main/known-addresses.json"

# Neo4j Configuration
NEO4J_URL=bolt://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=password
NEO4J_DATABASE=neo4j
490 changes: 408 additions & 82 deletions api/package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@nestjs/core": "^10.3.9",
"@nestjs/graphql": "^12.1.1",
"@nestjs/platform-express": "^10.3.9",
"@nestjs/typeorm": "^11.0.0",
"@parse/node-apn": "^6.0.1",
"@prisma/client": "^5.19.0",
"@repeaterjs/repeater": "^3.0.6",
Expand All @@ -49,6 +50,8 @@
"bluebird": "^3.7.2",
"bn.js": "^5.2.1",
"bullmq": "^5.8.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"csv-stringify": "^6.5.0",
"d3-array": "^3.2.4",
"decimal.js": "^10.4.3",
Expand All @@ -59,6 +62,7 @@
"lodash": "^4.17.21",
"maxmind": "^4.3.20",
"nats": "^2.26.0",
"neo4j-driver": "^5.17.0",
"path": "^0.12.7",
"qs": "^6.12.1",
"reflect-metadata": "^0.2.2",
Expand Down
2 changes: 2 additions & 0 deletions api/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import config from '../config/config.js';
import { AppService } from './app.service.js';
import { ClickhouseModule } from '../clickhouse/clickhouse.module.js';
import { Neo4jModule } from '../neo4j/neo4j.module.js';
import { OlModule } from '../ol/ol.module.js';
import { S3Module } from '../s3/s3.module.js';
import { NodeWatcherModule } from '../node-watcher/node-watcher.module.js';
Expand Down Expand Up @@ -40,6 +41,7 @@ import { MultiSigModule } from '../multi-sig/multi-sig.module.js';

S3Module,
ClickhouseModule,
Neo4jModule,
OlModule,
NodeWatcherModule,
StatsModule,
Expand Down
116 changes: 116 additions & 0 deletions api/src/clickhouse/clickhouse.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export interface ClickhouseQueryResponse<T> {
statistics: { elapsed: number; rows_read: number; bytes_read: number };
}

// Version 0's timestamp is calculated by subtracting the interval between epoch 2 and 3 from epoch 2's timestamp
const V0_TIMESTAMP = 1701203279;

@Injectable()
export class ClickhouseService implements OnModuleInit, OnApplicationShutdown {
private insertQueries = new Map<string, string>();
Expand Down Expand Up @@ -150,4 +153,117 @@ export class ClickhouseService implements OnModuleInit, OnApplicationShutdown {
throw error;
}
}

/**
* Convert transaction versions to timestamps using the same method as ol.controller.ts
* @param versions Array of transaction versions to convert
* @returns Mapping of versions to timestamps (in seconds)
*/
public async convertVersionsToTimestamps(versions: number[]): Promise<Map<number, number>> {
if (!versions || versions.length === 0) {
return new Map();
}

try {
// Using the same query approach as in ol.controller.ts
const timestampQuery = `
WITH
"gen_txs" AS (
SELECT ("version" + 1) as "version"
FROM "genesis_transaction"
WHERE
"genesis_transaction"."version" IN (${versions.join(',')})
),

"txs" AS (
SELECT "timestamp", "version"
FROM "block_metadata_transaction"
WHERE "version" IN (SELECT "version" FROM "gen_txs")

UNION ALL

SELECT "timestamp", "version"
FROM "state_checkpoint_transaction"
WHERE "version" IN (SELECT "version" FROM "gen_txs")

UNION ALL

SELECT "timestamp", "version"
FROM "user_transaction"
WHERE "version" IN (SELECT "version" FROM "gen_txs")

UNION ALL

SELECT "timestamp", "version"
FROM "script"
WHERE "version" IN (SELECT "version" FROM "gen_txs")
),

"tx_timestamps" AS (
SELECT
toUInt64("txs"."timestamp" - 1) as "timestamp",
toUInt64("version" - 1) as "version"
FROM "txs"

UNION ALL

SELECT "timestamp", "version"
FROM "block_metadata_transaction"
WHERE "version" IN (${versions.join(',')})

UNION ALL

SELECT "timestamp", "version"
FROM "state_checkpoint_transaction"
WHERE "version" IN (${versions.join(',')})

UNION ALL

SELECT "timestamp", "version"
FROM "user_transaction"
WHERE "version" IN (${versions.join(',')})

UNION ALL

SELECT "timestamp", "version"
FROM "script"
WHERE "version" IN (${versions.join(',')})
)

SELECT "timestamp", "version"
FROM "tx_timestamps"
ORDER BY "version" ASC
`;

const resultSet = await this.client.query({
query: timestampQuery,
format: 'JSONEachRow',
});

const rows = await resultSet.json<{
timestamp: string;
version: string;
}>();

// Create a map of version to timestamp (converted to seconds)
const versionToTimestampMap = new Map<number, number>();

rows.forEach(row => {
const version = parseInt(row.version, 10);
// Convert from microseconds to seconds
const timestamp = Math.floor(parseInt(row.timestamp, 10) / 1_000_000);
versionToTimestampMap.set(version, timestamp);
});

// Handle version 0 with hardcoded timestamp if needed
if (versions.includes(0) && !versionToTimestampMap.has(0)) {
versionToTimestampMap.set(0, V0_TIMESTAMP);
}

return versionToTimestampMap;
} catch (error) {
this.logger.error(`Error converting versions to timestamps: ${error.message}`);
throw error;
}
}
}
7 changes: 7 additions & 0 deletions api/src/config/config.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface Config {
apn?: ApnConfig;
firebase?: FirebaseConfig;
nats: NatsConfig;
neo4j: Neo4jConfig;
}

export interface InfoConfig {
Expand Down Expand Up @@ -56,3 +57,9 @@ export interface FirebaseConfig {
export interface NatsConfig {
servers: string;
}

export interface Neo4jConfig {
url: string;
username: string;
password: string;
}
6 changes: 6 additions & 0 deletions api/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ export default (): Config => {
nats: {
servers: ENV.NATS_SERVERS!,
},

neo4j: {
url: ENV.NEO4J_URL || 'bolt://localhost:7687',
username: ENV.NEO4J_USERNAME || 'neo4j',
password: ENV.NEO4J_PASSWORD || 'password',
},
};

return config;
Expand Down
10 changes: 10 additions & 0 deletions api/src/neo4j/dto/query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IsObject, IsOptional, IsString } from 'class-validator';

export class CypherQueryDto {
@IsString()
query: string;

@IsObject()
@IsOptional()
params?: Record<string, any>;
}
28 changes: 28 additions & 0 deletions api/src/neo4j/neo4j.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Body, Controller, Get, Logger, Param, Post } from '@nestjs/common';
import { Neo4jService } from './neo4j.service.js';
import { CypherQueryDto } from './dto/query.dto.js';

@Controller('neo4j')
export class Neo4jController {
private readonly logger = new Logger(Neo4jController.name);

constructor(private readonly neo4jService: Neo4jService) {}

// @Post('query')
// async executeQuery(@Body() queryDto: CypherQueryDto) {
// this.logger.debug(`Executing Neo4j query: ${queryDto.query}`);
// try {
// const results = await this.neo4jService.runQuery(queryDto.query, queryDto.params || {});
// return { results };
// } catch (error) {
// this.logger.error(`Error executing Neo4j query: ${error.message}`, error.stack);
// throw error;
// }
// }

@Get('wallet/:address/connections')
async getWalletConnections(@Param('address') address: string) {
this.logger.debug(`Fetching connections for wallet: ${address}`);
return this.neo4jService.getAllWalletFirstDegree(address);
}
}
10 changes: 10 additions & 0 deletions api/src/neo4j/neo4j.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { Neo4jService } from './neo4j.service.js';
import { Neo4jController } from './neo4j.controller.js';

@Module({
providers: [Neo4jService],
exports: [Neo4jService],
controllers: [Neo4jController],
})
export class Neo4jModule {}
Loading