Skip to content

Commit

Permalink
Update stellar sdk, fix events not being indexed
Browse files Browse the repository at this point in the history
  • Loading branch information
stwiname committed Feb 4, 2025
1 parent a528350 commit eafc1e4
Show file tree
Hide file tree
Showing 16 changed files with 135 additions and 107 deletions.
2 changes: 1 addition & 1 deletion packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@nestjs/event-emitter": "^2.0.0",
"@nestjs/platform-express": "^9.4.0",
"@nestjs/schedule": "^3.0.1",
"@stellar/stellar-sdk": "^12.2.0",
"@stellar/stellar-sdk": "^13.1.0",
"@subql/common": "^5.3.0",
"@subql/common-stellar": "workspace:*",
"@subql/node-core": "^16.2.0",
Expand Down
1 change: 0 additions & 1 deletion packages/node/src/indexer/fetch.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
ConnectionPoolStateManager,
NodeConfig,
IProjectUpgradeService,
ProjectUpgradeService,
InMemoryCacheService,
MonitorService,
CoreModule,
Expand Down
2 changes: 1 addition & 1 deletion packages/node/src/indexer/indexer.manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class IndexerManager extends BaseIndexerManager<
}

protected async indexBlockData(
{ block, effects, operations, transactions }: StellarBlockWrapper,
{ block, transactions }: StellarBlockWrapper,
dataSources: SubqlDatasource[],
getVM: (d: SubqlDatasource) => Promise<IndexerSandbox>,
): Promise<void> {
Expand Down
4 changes: 1 addition & 3 deletions packages/node/src/stellar/api.connection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import { EventEmitter2 } from '@nestjs/event-emitter';
import {
ApiConnectionError,
ApiErrorType,
Expand Down Expand Up @@ -41,7 +40,7 @@ describe('StellarApiConnection', () => {

beforeEach(async () => {
sorobanApi = new SorobanServer(SOROBAN_ENDPOINT);
unsafeApi = new StellarApi(HTTP_ENDPOINT, new EventEmitter2(), sorobanApi);
unsafeApi = new StellarApi(HTTP_ENDPOINT, sorobanApi);
await unsafeApi.init();
apiConnection = new StellarApiConnection(unsafeApi, fetchBlockBatches);
});
Expand All @@ -51,7 +50,6 @@ describe('StellarApiConnection', () => {
await StellarApiConnection.create(
HTTP_ENDPOINT,
fetchBlockBatches,
new EventEmitter2(),
sorobanApi,
),
).toBeInstanceOf(StellarApiConnection);
Expand Down
4 changes: 1 addition & 3 deletions packages/node/src/stellar/api.connection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import { EventEmitter2 } from '@nestjs/event-emitter';
import {
ApiConnectionError,
ApiErrorType,
Expand Down Expand Up @@ -50,11 +49,10 @@ export class StellarApiConnection
static async create(
endpoint: string,
fetchBlockBatches: FetchFunc,
eventEmitter: EventEmitter2,
soroban?: SorobanServer,
config?: IStellarEndpointConfig,
): Promise<StellarApiConnection> {
const api = new StellarApi(endpoint, eventEmitter, soroban, config);
const api = new StellarApi(endpoint, soroban, config);

await api.init();

Expand Down
5 changes: 2 additions & 3 deletions packages/node/src/stellar/api.service.stellar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import { INestApplication } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { Test } from '@nestjs/testing';
import { scValToNative } from '@stellar/stellar-sdk';
import { ConnectionPoolService, delay, NodeConfig } from '@subql/node-core';
import { ConnectionPoolService, NodeConfig } from '@subql/node-core';
import { ConnectionPoolStateManager } from '@subql/node-core/dist';
import { GraphQLSchema } from 'graphql';
import { range, some } from 'lodash';
import { range } from 'lodash';
import { SubqueryProject } from '../configure/SubqueryProject';
import { StellarApiService } from './api.service.stellar';
import { StellarApi } from './api.stellar';
import { StellarBlockWrapped } from './block.stellar';

const HTTP_ENDPOINT = 'https://horizon-futurenet.stellar.org';
const SOROBAN_ENDPOINT = 'https://rpc-futurenet.stellar.org';
Expand Down
7 changes: 1 addition & 6 deletions packages/node/src/stellar/api.service.stellar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@

import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
StellarProjectNetwork,
StellarProjectNetworkConfig,
} from '@subql/common-stellar';
import { StellarProjectNetworkConfig } from '@subql/common-stellar';
import {
ApiService,
ConnectionPoolService,
getLogger,
ProjectUpgradeService,
IBlock,
exitWithError,
NodeConfig,
Expand Down Expand Up @@ -92,7 +88,6 @@ export class StellarApiService extends ApiService<
StellarApiConnection.create(
endpoint,
this.fetchBlockBatches,
this.eventEmitter,
sorobanClient,
config,
),
Expand Down
3 changes: 1 addition & 2 deletions packages/node/src/stellar/api.stellar.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import { EventEmitter2 } from '@nestjs/event-emitter';
import { StellarApi } from './api.stellar';
import { SorobanServer } from './soroban.server';

Expand All @@ -12,7 +11,7 @@ jest.setTimeout(60000);

const prepareStellarApi = async function () {
const soroban = new SorobanServer(SOROBAN_ENDPOINT);
const api = new StellarApi(HTTP_ENDPOINT, new EventEmitter2(), soroban);
const api = new StellarApi(HTTP_ENDPOINT, soroban);
await api.init();
return api;
};
Expand Down
41 changes: 13 additions & 28 deletions packages/node/src/stellar/api.stellar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-3.0

import assert from 'assert';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Horizon } from '@stellar/stellar-sdk';
import { getLogger, IBlock } from '@subql/node-core';
import {
Expand All @@ -22,9 +21,6 @@ import { SorobanServer } from './soroban.server';
import { StellarServer } from './stellar.server';
import { formatBlockUtil } from './utils.stellar';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { version: packageVersion } = require('../../package.json');

const logger = getLogger('api.Stellar');

export class StellarApi implements ApiWrapper {
Expand All @@ -35,9 +31,8 @@ export class StellarApi implements ApiWrapper {

constructor(
private endpoint: string,
private eventEmitter: EventEmitter2,
private _sorobanClient?: SorobanServer,
private config?: IStellarEndpointConfig,
config?: IStellarEndpointConfig,
) {
const { hostname, protocol, searchParams } = new URL(this.endpoint);

Expand Down Expand Up @@ -140,18 +135,6 @@ export class StellarApi implements ApiWrapper {
return effects;
}

private getTransactionApplicationOrder(eventId: string) {
// Right shift the ID by 12 bits to exclude the Operation Index
const shiftedId = BigInt(eventId.split('-')[0]) >> BigInt(12);

// Create a mask for 20 bits to ignore the Ledger Sequence Number
const mask = BigInt((1 << 20) - 1);

// Apply bitwise AND operation with the mask to get the Transaction Application Order
const transactionApplicationOrder = shiftedId & mask;
return Number(transactionApplicationOrder);
}

private getOperationIndex(id: string) {
// Pick the first part of the ID before the '-' character
const idPart = id.split('-')[0];
Expand Down Expand Up @@ -198,8 +181,6 @@ export class StellarApi implements ApiWrapper {

private wrapOperationsForTx(
transactionId: string,
applicationOrder: number,
sequence: number,
operationsForSequence: Horizon.ServerApi.OperationRecord[],
effectsForSequence: Horizon.ServerApi.EffectRecord[],
eventsForSequence: SorobanEvent[],
Expand All @@ -208,14 +189,20 @@ export class StellarApi implements ApiWrapper {
(op) => op.transaction_hash === transactionId,
);

const events = eventsForSequence.filter(
(evt) => evt.txHash === transactionId,
);

// If there are soroban events then there should only be a single operation.
// This check is here in case there are furture changes to the network.
assert(
events.length > 0 ? operations.length === 1 : true,
'Unable to assign events to multiple operations',
);

return operations.map((op, index) => {
const effects = this.wrapEffectsForOperation(index, effectsForSequence);

const events = eventsForSequence.filter(
(event) =>
this.getTransactionApplicationOrder(event.id) === applicationOrder,
);

const wrappedOp: StellarOperation = {
...op,
ledger: null,
Expand All @@ -242,7 +229,7 @@ export class StellarApi implements ApiWrapper {
effectsForSequence: Horizon.ServerApi.EffectRecord[],
eventsForSequence: SorobanEvent[],
): StellarTransaction[] {
return transactions.map((tx, index) => {
return transactions.map((tx) => {
const wrappedTx: StellarTransaction = {
...tx,
ledger: null,
Expand All @@ -256,8 +243,6 @@ export class StellarApi implements ApiWrapper {
// TODO, this include other attribute from HorizonApi.TransactionResponse, but type assertion incorrect
// TransactionRecord extends Omit<HorizonApi.TransactionResponse, "created_at">
(tx as any).id,
index + 1,
sequence,
operationsForSequence,
effectsForSequence,
eventsForSequence,
Expand Down
30 changes: 13 additions & 17 deletions packages/node/src/stellar/safe-api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import { Memo, MemoType, Operation } from '@stellar/stellar-base';
import {
Account,
Address,
Contract,
FeeBumpTransaction,
SorobanRpc,
Transaction,
xdr,
rpc,
Expand All @@ -21,9 +19,9 @@ const logger = getLogger('safe.api.stellar');

export default class SafeStellarProvider extends SorobanServer {
private blockHeight: number;
private baseApi: SorobanRpc.Server;
private baseApi: rpc.Server;

constructor(baseApi: SorobanRpc.Server, blockHeight: number) {
constructor(baseApi: rpc.Server, blockHeight: number) {
super(baseApi.serverURL.toString());
this.blockHeight = blockHeight;
this.baseApi = baseApi;
Expand All @@ -35,7 +33,7 @@ export default class SafeStellarProvider extends SorobanServer {
}

//eslint-disable-next-line @typescript-eslint/require-await
async getHealth(): Promise<SorobanRpc.Api.GetHealthResponse> {
async getHealth(): Promise<rpc.Api.GetHealthResponse> {
throw new Error('Method getHealth is not implemented.');
}

Expand All @@ -44,62 +42,60 @@ export default class SafeStellarProvider extends SorobanServer {
contract: string | Address | Contract,
key: xdr.ScVal,
durability: rpc.Durability = rpc.Durability.Persistent,
): Promise<SorobanRpc.Api.LedgerEntryResult> {
): Promise<rpc.Api.LedgerEntryResult> {
throw new Error('Method getContractData is not implemented.');
}

// @ts-ignore
// eslint-disable-next-line @typescript-eslint/require-await
async getLedgerEntries(
keys: xdr.LedgerKey[],
): Promise<SorobanRpc.Api.GetLedgerEntriesResponse> {
): Promise<rpc.Api.GetLedgerEntriesResponse> {
throw new Error('Method getLedgerEntries is not implemented.');
}

//eslint-disable-next-line @typescript-eslint/require-await
async getTransaction(
hash: string,
): Promise<SorobanRpc.Api.GetTransactionResponse> {
async getTransaction(hash: string): Promise<rpc.Api.GetTransactionResponse> {
throw new Error('Method getTransaction is not implemented.');
}

async getEvents(
request: SorobanRpc.Server.GetEventsRequest,
): Promise<SorobanRpc.Api.GetEventsResponse> {
request: rpc.Server.GetEventsRequest,
): Promise<rpc.Api.GetEventsResponse> {
return this.baseApi.getEvents({
startLedger: this.blockHeight,
filters: [],
});
}

//eslint-disable-next-line @typescript-eslint/require-await
async getNetwork(): Promise<SorobanRpc.Api.GetNetworkResponse> {
async getNetwork(): Promise<rpc.Api.GetNetworkResponse> {
throw new Error('Method getNetwork is not implemented.');
}

//eslint-disable-next-line @typescript-eslint/require-await
async getLatestLedger(): Promise<SorobanRpc.Api.GetLatestLedgerResponse> {
async getLatestLedger(): Promise<rpc.Api.GetLatestLedgerResponse> {
throw new Error('Method getLatestLedger is not implemented.');
}

//eslint-disable-next-line @typescript-eslint/require-await
async simulateTransaction(
transaction: Transaction | FeeBumpTransaction,
): Promise<SorobanRpc.Api.SimulateTransactionResponse> {
): Promise<rpc.Api.SimulateTransactionResponse> {
throw new Error('Method simulateTransaction is not implemented.');
}

//eslint-disable-next-line @typescript-eslint/require-await
async prepareTransaction(
transaction: Transaction | FeeBumpTransaction,
): Promise<Transaction<Memo<MemoType>, Operation[]>> {
): Promise<Transaction> {
throw new Error('Method prepareTransaction is not implemented.');
}

//eslint-disable-next-line @typescript-eslint/require-await
async sendTransaction(
transaction: Transaction | FeeBumpTransaction,
): Promise<SorobanRpc.Api.SendTransactionResponse> {
): Promise<rpc.Api.SendTransactionResponse> {
throw new Error('Method sendTransaction is not implemented.');
}

Expand Down
Loading

0 comments on commit eafc1e4

Please sign in to comment.