Skip to content

Commit

Permalink
fix: resolve conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
brancoder committed Feb 18, 2024
2 parents fa78224 + 61ff442 commit 880d17d
Show file tree
Hide file tree
Showing 44 changed files with 1,164 additions and 296 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nova-build-temp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
TARGET_COMMIT:
description: "Target Commit Hash for the SDK"
required: false
default: "133f911b18191cda9099f1b4aeaf7d0022dfe0fb"
default: "8f0ff5e1e899a0d960ddfea09237739a88c3bcf1"
environment:
type: choice
description: "Select the environment to deploy to"
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/pr-test-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: PR test check

on:
pull_request:
types: [opened, reopened, synchronize]

jobs:
build-check:
strategy:
fail-fast: false
matrix:
project: [client]
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ matrix.project }}
steps:
- uses: actions/checkout@v2

- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "16"

- name: Install Dependencies
run: npm install

- name: Tests Check
run: npm run test run
4 changes: 2 additions & 2 deletions api/package-lock.json

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

2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "explorer-api",
"description": "API for Tangle Explorer",
"version": "3.3.3-rc.1",
"version": "3.3.4-rc.1",
"author": "Martyn Janes <[email protected]>",
"repository": {
"type": "git",
Expand Down
3 changes: 2 additions & 1 deletion api/src/models/api/nova/feed/IFeedUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-redundant-type-constituents */
import { Block, IBlockMetadata } from "@iota/sdk-nova";
import { Block, IBlockMetadata, SlotIndex } from "@iota/sdk-nova";

interface IFeedBlockUpdate {
blockId: string;
Expand All @@ -11,4 +11,5 @@ export interface IFeedUpdate {
subscriptionId: string;
blockUpdate?: IFeedBlockUpdate;
blockMetadataUpdate?: IBlockMetadata;
slotFinalized?: SlotIndex;
}
2 changes: 1 addition & 1 deletion api/src/models/db/networkType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
const LEGACY_MAINNET = "legacy-mainnet";
const CHRYSALIS_MAINNET = "chrysalis-mainnet";
const MAINNET = "mainnet";
export const MAINNET = "mainnet";
const DEVNET = "devnet";
export const SHIMMER = "shimmer";
const TESTNET = "testnet";
Expand Down
8 changes: 8 additions & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export const routes: IRoute[] = [
folder: "chrysalis/transactionhistory",
func: "get",
},
{
path: "/transactionhistory/download/:network/:address",
method: "post",
folder: "chrysalis/transactionhistory/download",
func: "post",
dataBody: true,
dataResponse: true,
},
{
path: "/chrysalis/did/:network/:did/document",
method: "get",
Expand Down
147 changes: 147 additions & 0 deletions api/src/routes/chrysalis/transactionhistory/download/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { UnitsHelper } from "@iota/iota.js";
import JSZip from "jszip";
import moment from "moment";
import { ServiceFactory } from "../../../../factories/serviceFactory";
import logger from "../../../../logger";
import { IDataResponse } from "../../../../models/api/IDataResponse";
import { ITransactionHistoryDownloadBody } from "../../../../models/api/stardust/chronicle/ITransactionHistoryDownloadBody";
import { ITransactionHistoryRequest } from "../../../../models/api/stardust/chronicle/ITransactionHistoryRequest";
import { IConfiguration } from "../../../../models/configuration/IConfiguration";
import { CHRYSALIS } from "../../../../models/db/protocolVersion";
import { NetworkService } from "../../../../services/networkService";
import { ChrysalisTangleHelper } from "../../../../utils/chrysalis/chrysalisTangleHelper";
import { ValidationHelper } from "../../../../utils/validationHelper";

interface IParsedRow {
messageId: string;
transactionId: string;
referencedByMilestoneIndex: string;
milestoneTimestampReferenced: string;
timestampFormatted: string;
ledgerInclusionState: string;
conflictReason: string;
inputsCount: string;
outputsCount: string;
addressBalanceChange: string;
addressBalanceChangeFormatted: string;
}

/**
* Download the transaction history from chronicle stardust.
* @param _ The configuration.
* @param request The request.
* @param body The request body
* @returns The response.
*/
export async function post(
_: IConfiguration,
request: ITransactionHistoryRequest,
body: ITransactionHistoryDownloadBody,
): Promise<IDataResponse | null> {
const networkService = ServiceFactory.get<NetworkService>("network");
ValidationHelper.oneOf(request.network, networkService.networkNames(), "network");

const networkConfig = networkService.get(request.network);

if (networkConfig.protocolVersion !== CHRYSALIS || !networkConfig.permaNodeEndpoint || !request.address) {
return null;
}

const transactionHistoryDownload: string = await ChrysalisTangleHelper.transactionHistoryDownload(networkConfig, request.address);

const parsed = parseResponse(transactionHistoryDownload);

let csvContent = `${["Timestamp", "TransactionId", "Balance changes"].join(",")}\n`;

const filtered = parsed.body.filter((row) => {
return moment(row.milestoneTimestampReferenced).isAfter(body.targetDate);
});

for (const i of filtered) {
const row = [i.timestampFormatted, i.transactionId, i.addressBalanceChangeFormatted].join(",");
csvContent += `${row}\n`;
}

const jsZip = new JSZip();
let response: IDataResponse = null;

try {
jsZip.file("history.csv", csvContent);
const content = await jsZip.generateAsync({ type: "nodebuffer" });

response = {
data: content,
contentType: "application/octet-stream",
};
} catch (e) {
logger.error(`Failed to zip transaction history for download. Cause: ${e}`);
}

return response;
}

/**
* Split response into lines, format each line
* @param response The response from endpoint to parse.
* @returns Object with headers and body.
*/
function parseResponse(response: string) {
const lines = response.split("\n");
let isHeadersSet = false;
let headers: IParsedRow; // Headers: "MessageID", "TransactionID", "ReferencedByMilestoneIndex", "MilestoneTimestampReferenced", "LedgerInclusionState", "ConflictReason", "InputsCount", "OutputsCount", "AddressBalanceChange"
const body: IParsedRow[] = [];

for (const line of lines) {
const row = parseRow(line);

if (row) {
if (isHeadersSet) {
body.push(row);
} else {
headers = row;
isHeadersSet = true;
}
}
}

return { headers, body };
}

/**
* @param row The row to parse.
* @returns Object with parsed and formatted values.
*/
function parseRow(row: string): IParsedRow {
const cols = row.split(",");
if (!cols || cols.length < 9) {
return null;
}

const [
messageId,
transactionId,
referencedByMilestoneIndex,
milestoneTimestampReferenced,
ledgerInclusionState,
conflictReason,
inputsCount,
outputsCount,
addressBalanceChange,
] = cols;

const timestamp = milestoneTimestampReferenced.replaceAll("\"", "");

return {
messageId,
transactionId,
referencedByMilestoneIndex,
milestoneTimestampReferenced: timestamp,
timestampFormatted: moment(timestamp).format("YYYY-MM-DD HH:mm:ss"),
ledgerInclusionState,
conflictReason,
inputsCount,
outputsCount,
addressBalanceChange,
addressBalanceChangeFormatted: UnitsHelper.formatUnits(Number(addressBalanceChange), "Mi"),
};
}
51 changes: 45 additions & 6 deletions api/src/routes/stardust/transactionhistory/download/post.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OutputResponse, INodeInfoBaseToken, CommonOutput } from "@iota/sdk";
import { CommonOutput, INodeInfoBaseToken, OutputResponse } from "@iota/sdk";
import JSZip from "jszip";
import moment from "moment";
import { ServiceFactory } from "../../../../factories/serviceFactory";
Expand All @@ -8,6 +8,8 @@ import { ITransactionHistoryDownloadBody } from "../../../../models/api/stardust
import { ITransactionHistoryRequest } from "../../../../models/api/stardust/chronicle/ITransactionHistoryRequest";
import { ITransactionHistoryItem } from "../../../../models/api/stardust/chronicle/ITransactionHistoryResponse";
import { IConfiguration } from "../../../../models/configuration/IConfiguration";
import { INetwork } from "../../../../models/db/INetwork";
import { MAINNET } from "../../../../models/db/networkType";
import { STARDUST } from "../../../../models/db/protocolVersion";
import { NetworkService } from "../../../../services/networkService";
import { ChronicleService } from "../../../../services/stardust/chronicleService";
Expand All @@ -20,13 +22,16 @@ export interface ITransactionHistoryRecord {
isGenesisByDate: boolean;
isSpent: boolean;
transactionId: string;
outputIdFromSupplyIncrease?: string;
timestamp: number;
dateFormatted: string;
balanceChange: number;
balanceChangeFormatted: string;
outputs: OutputWithDetails[];
}

const STARDUST_GENESIS_MILESTONE = 7669900;

/**
* Download the transaction history from chronicle stardust.
* @param _ The configuration.
Expand Down Expand Up @@ -73,14 +78,21 @@ export async function post(
});
const tokenInfo = nodeInfoService.getNodeInfo().baseToken;

const transactions = getTransactionHistoryRecords(groupOutputsByTransactionId(fulfilledOutputs), tokenInfo);
const transactions = getTransactionHistoryRecords(groupOutputsByTransactionId(fulfilledOutputs), tokenInfo, networkConfig);

const headers = ["Timestamp", "TransactionId", "Balance changes"];

let csvContent = `${headers.join(",")}\n`;

for (const transaction of transactions) {
const row = [transaction.dateFormatted, transaction.transactionId, transaction.balanceChangeFormatted].join(",");
let transactionCell = transaction.transactionId;
if (transaction.isGenesisByDate) {
transactionCell = `Stardust Genesis (Chrysalis): ${transactionCell}`;
if (transaction.outputIdFromSupplyIncrease) {
transactionCell = `Supply Increase Output: ${transaction.outputIdFromSupplyIncrease}`;
}
}
const row = [transaction.dateFormatted, transactionCell, transaction.balanceChangeFormatted].join(",");
csvContent += `${row}\n`;
}

Expand Down Expand Up @@ -140,21 +152,26 @@ export const groupOutputsByTransactionId = (outputsWithDetails: OutputWithDetail
export const getTransactionHistoryRecords = (
transactionIdToOutputs: Map<string, OutputWithDetails[]>,
tokenInfo: INodeInfoBaseToken,
networkConfig: INetwork,
): ITransactionHistoryRecord[] => {
const calculatedTransactions: ITransactionHistoryRecord[] = [];

for (const [transactionId, outputs] of transactionIdToOutputs.entries()) {
const lastOutputTime = Math.max(...outputs.map((t) => t.milestoneTimestamp));
const balanceChange = calculateBalanceChange(outputs);

const isGenesisByDate = outputs.map((t) => t.milestoneTimestamp).includes(0);

const isSpent = balanceChange <= 0;
const isGenesisByDate = outputs.some((output) => output.milestoneIndex === STARDUST_GENESIS_MILESTONE);

let outputIdFromSupplyIncrease;
if (isGenesisByDate) {
outputIdFromSupplyIncrease = getStardustGenesisOutputId(outputs, networkConfig, transactionId);
}

calculatedTransactions.push({
isGenesisByDate,
isSpent,
transactionId,
outputIdFromSupplyIncrease,
timestamp: lastOutputTime,
dateFormatted: moment(lastOutputTime * 1000).format("YYYY-MM-DD HH:mm:ss"),
balanceChange,
Expand All @@ -165,6 +182,28 @@ export const getTransactionHistoryRecords = (
return calculatedTransactions;
};

/**
* Get the output id from the stardust genesis.
* @param outputs List of outputs related to transaction
* @param networkConfig Network configuration
* @param transactionId Current trancaction
* @returns The output id from the stardust genesis or undefined.
*/
function getStardustGenesisOutputId(outputs: OutputWithDetails[], networkConfig: INetwork, transactionId: string): string | undefined {
const STARDUST_SUPPLY_INCREASE_OUTPUT_TICKER = "0xb191c4bc825ac6983789e50545d5ef07a1d293a98ad974fc9498cb18";

const outputFromStardustGenesis = outputs.find((output) => {
if (
networkConfig.network === MAINNET &&
output.milestoneIndex === STARDUST_GENESIS_MILESTONE &&
transactionId.includes(STARDUST_SUPPLY_INCREASE_OUTPUT_TICKER)
) {
return true;
}
});
return outputFromStardustGenesis?.outputId;
}

export const calculateBalanceChange = (outputs: OutputWithDetails[]) => {
let totalAmount = 0;

Expand Down
18 changes: 17 additions & 1 deletion api/src/services/nova/feed/novaFeed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Block, Client, IBlockMetadata } from "@iota/sdk-nova";
import { Block, Client, IBlockMetadata, SlotCommitment } from "@iota/sdk-nova";
import { ClassConstructor, plainToInstance } from "class-transformer";
import { ServiceFactory } from "../../../factories/serviceFactory";
import logger from "../../../logger";
Expand Down Expand Up @@ -112,6 +112,22 @@ export class NovaFeed {
logger.error("[NovaFeed]: Failed broadcasting block-metadata downstream.");
}
});

// eslint-disable-next-line no-void
void this._mqttClient.listenMqtt(["commitments/finalized"], async (_, message) => {
try {
const deserializedMessage: { topic: string; payload: string } = JSON.parse(message);
const slotCommitment: SlotCommitment = JSON.parse(deserializedMessage.payload);
const slotFinalized = slotCommitment.slot;

const update: Partial<IFeedUpdate> = { slotFinalized };

// eslint-disable-next-line no-void
void this.broadcastBlock(update);
} catch {
logger.error("[NovaFeed]: Failed broadcasting finalized slot downstream.");
}
});
}

private parseMqttPayloadMessage<T>(cls: ClassConstructor<T>, serializedMessage: string): T {
Expand Down
Loading

0 comments on commit 880d17d

Please sign in to comment.