Merge remote-tracking branch 'origin/feature/blockchain-watcher-solana-log' into feature/blockchain-watcher-solana-log
This commit is contained in:
commit
631d19cb65
|
@ -0,0 +1,11 @@
|
|||
## Link Issue
|
||||
|
||||
<-- Link issue !-->
|
||||
|
||||
## Description
|
||||
|
||||
<-- Description of issue !-->
|
||||
|
||||
## Test or Screensshots
|
||||
|
||||
<-- How test this change !-->
|
|
@ -1,15 +1,18 @@
|
|||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
collectCoverageFrom: [
|
||||
"./src/domain",
|
||||
"./src/infrastructure/mappers",
|
||||
"./src/infrastructure/repositories",
|
||||
],
|
||||
moduleFileExtensions: ["js", "json", "ts"],
|
||||
roots: ["test", "src"],
|
||||
testRegex: ".*\\.test\\.ts$",
|
||||
transform: {
|
||||
"^.+\\.(t|j)s$": "ts-jest",
|
||||
},
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ["**/*.(t|j)s"],
|
||||
coveragePathIgnorePatterns: ["node_modules", "test"],
|
||||
coverageDirectory: "./coverage",
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 85,
|
||||
lines: 55,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"test:coverage": "jest --collectCoverage=true",
|
||||
"build": "tsc",
|
||||
"dev": "ts-node src/start.ts",
|
||||
"dev:debug": "ts-node src/start.ts start --debug --watch",
|
||||
"prettier": "prettier --write ."
|
||||
},
|
||||
"author": "chase-45",
|
||||
|
|
|
@ -2,39 +2,3 @@ export * from "./evm";
|
|||
export * from "./events";
|
||||
export * from "./jobs";
|
||||
export * as solana from "./solana";
|
||||
|
||||
export class Fallible<R, E> {
|
||||
private error?: E;
|
||||
private value?: R;
|
||||
|
||||
constructor(value?: R, error?: E) {
|
||||
this.value = value;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public getValue(): R {
|
||||
if (!this.value) {
|
||||
throw this.error ? this.error : new Error("No value and no error present");
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public getError(): E {
|
||||
if (!this.error) {
|
||||
throw new Error("No error present");
|
||||
}
|
||||
return this.error;
|
||||
}
|
||||
|
||||
public isOk(): boolean {
|
||||
return !this.error && this.value !== undefined;
|
||||
}
|
||||
|
||||
public static ok<R, E>(value: R): Fallible<R, E> {
|
||||
return new Fallible<R, E>(value);
|
||||
}
|
||||
|
||||
public static error<R, E>(error: E): Fallible<R, E> {
|
||||
return new Fallible<R, E>(undefined, error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,28 +59,3 @@ export enum ErrorType {
|
|||
SkippedSlot,
|
||||
NoBlockOrBlockTime,
|
||||
}
|
||||
|
||||
export class Failure extends Error {
|
||||
readonly code?: number | unknown;
|
||||
readonly type?: ErrorType;
|
||||
constructor(code: number | unknown, message: string, type?: ErrorType) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
|
||||
if (this.code === -32007 || this.code === -32009) {
|
||||
this.type = ErrorType.SkippedSlot;
|
||||
}
|
||||
|
||||
if (type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public skippedSlot(): boolean {
|
||||
return this.type === ErrorType.SkippedSlot;
|
||||
}
|
||||
|
||||
public noBlockOrBlockTime(): boolean {
|
||||
return this.type === ErrorType.NoBlockOrBlockTime;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
export class Fallible<R, E> {
|
||||
private value?: R;
|
||||
private error?: E;
|
||||
|
||||
constructor(value?: R, error?: E) {
|
||||
this.value = value;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public getValue(): R {
|
||||
if (!this.value) {
|
||||
throw this.error ? this.error : new Error("No value and no error present");
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public getError(): E {
|
||||
if (!this.error) {
|
||||
throw new Error("No error present");
|
||||
}
|
||||
return this.error;
|
||||
}
|
||||
|
||||
public isOk(): boolean {
|
||||
return !this.error && this.value !== undefined;
|
||||
}
|
||||
|
||||
public static ok<R, E>(value: R): Fallible<R, E> {
|
||||
return new Fallible<R, E>(value);
|
||||
}
|
||||
|
||||
public static error<R, E>(error: E): Fallible<R, E> {
|
||||
return new Fallible<R, E>(undefined, error);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
export enum ErrorType {
|
||||
SkippedSlot,
|
||||
NoBlockOrBlockTime,
|
||||
}
|
||||
|
||||
export class SolanaFailure extends Error {
|
||||
readonly code?: number | unknown;
|
||||
readonly type?: ErrorType;
|
||||
|
||||
constructor(code: number | unknown, message: string, type?: ErrorType) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
|
||||
if (this.code === -32007 || this.code === -32009) {
|
||||
this.type = ErrorType.SkippedSlot;
|
||||
}
|
||||
|
||||
if (type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public skippedSlot(): boolean {
|
||||
return this.type === ErrorType.SkippedSlot;
|
||||
}
|
||||
|
||||
public noBlockOrBlockTime(): boolean {
|
||||
return this.type === ErrorType.NoBlockOrBlockTime;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./SolanaFailure";
|
||||
export * from "./Fallible";
|
|
@ -1,14 +1,7 @@
|
|||
import { RunPollingJob } from "./actions/RunPollingJob";
|
||||
import {
|
||||
EvmBlock,
|
||||
EvmLog,
|
||||
EvmLogFilter,
|
||||
Fallible,
|
||||
Handler,
|
||||
JobDefinition,
|
||||
solana,
|
||||
} from "./entities";
|
||||
import { EvmBlock, EvmLog, EvmLogFilter, Handler, JobDefinition, solana } from "./entities";
|
||||
import { ConfirmedSignatureInfo } from "./entities/solana";
|
||||
import { Fallible, SolanaFailure } from "./errors";
|
||||
|
||||
export interface EvmBlockRepository {
|
||||
getBlockHeight(finality: string): Promise<bigint>;
|
||||
|
@ -18,7 +11,7 @@ export interface EvmBlockRepository {
|
|||
|
||||
export interface SolanaSlotRepository {
|
||||
getLatestSlot(commitment: string): Promise<number>;
|
||||
getBlock(slot: number, finality?: string): Promise<Fallible<solana.Block, solana.Failure>>;
|
||||
getBlock(slot: number, finality?: string): Promise<Fallible<solana.Block, SolanaFailure>>;
|
||||
getSignaturesForAddress(
|
||||
address: string,
|
||||
beforeSig: string,
|
||||
|
|
|
@ -5,13 +5,13 @@ import {
|
|||
SnsEventRepository,
|
||||
EvmJsonRPCBlockRepository,
|
||||
EvmJsonRPCBlockRepositoryCfg,
|
||||
FileMetadataRepo,
|
||||
FileMetadataRepository,
|
||||
PromStatRepository,
|
||||
StaticJobRepository,
|
||||
Web3SolanaSlotRepository,
|
||||
} from "./repositories";
|
||||
|
||||
import { HttpClient } from "./repositories/HttpClient";
|
||||
import { HttpClient } from "./http/HttpClient";
|
||||
import { JobRepository } from "../domain/repositories";
|
||||
|
||||
export class RepositoriesBuilder {
|
||||
|
@ -31,7 +31,7 @@ export class RepositoriesBuilder {
|
|||
this.repositories.set("metrics", new PromStatRepository());
|
||||
|
||||
this.cfg.metadata?.dir &&
|
||||
this.repositories.set("metadata", new FileMetadataRepo(this.cfg.metadata.dir));
|
||||
this.repositories.set("metadata", new FileMetadataRepository(this.cfg.metadata.dir));
|
||||
|
||||
this.cfg.supportedChains.forEach((chain) => {
|
||||
if (!this.cfg.platforms[chain]) throw new Error(`No config for chain ${chain}`);
|
||||
|
@ -80,7 +80,7 @@ export class RepositoriesBuilder {
|
|||
return this.getRepo("sns");
|
||||
}
|
||||
|
||||
public getMetadataRepository(): FileMetadataRepo {
|
||||
public getMetadataRepository(): FileMetadataRepository {
|
||||
return this.getRepo("metadata");
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { EvmBlock, EvmLogFilter, EvmLog, EvmTag } from "../../domain/entities";
|
|||
import { EvmBlockRepository } from "../../domain/repositories";
|
||||
import { AxiosInstance } from "axios";
|
||||
import winston from "../log";
|
||||
import { HttpClient, HttpClientError } from "./HttpClient";
|
||||
import { HttpClient, HttpClientError } from "../http/HttpClient";
|
||||
|
||||
/**
|
||||
* EvmJsonRPCBlockRepository is a repository that uses a JSON RPC endpoint to fetch blocks.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import fs from "fs";
|
||||
import { MetadataRepository } from "../../domain/repositories";
|
||||
|
||||
export class FileMetadataRepo implements MetadataRepository<any> {
|
||||
export class FileMetadataRepository implements MetadataRepository<any> {
|
||||
private readonly dirPath: string;
|
||||
|
||||
constructor(dirPath: string) {
|
|
@ -15,13 +15,13 @@ import {
|
|||
SolanaSlotRepository,
|
||||
StatRepository,
|
||||
} from "../../domain/repositories";
|
||||
import { FileMetadataRepo, SnsEventRepository } from "./index";
|
||||
import { FileMetadataRepository, SnsEventRepository } from "./index";
|
||||
import { HandleSolanaTransactions } from "../../domain/actions/solana/HandleSolanaTransactions";
|
||||
import { solanaLogMessagePublishedMapper, evmLogMessagePublishedMapper } from "../mappers";
|
||||
import log from "../log";
|
||||
|
||||
export class StaticJobRepository implements JobRepository {
|
||||
private fileRepo: FileMetadataRepo;
|
||||
private fileRepo: FileMetadataRepository;
|
||||
private dryRun: boolean = false;
|
||||
private sources: Map<string, (def: JobDefinition) => RunPollingJob> = new Map();
|
||||
private handlers: Map<string, (cfg: any, target: string, mapper: any) => Promise<Handler>> =
|
||||
|
@ -45,7 +45,7 @@ export class StaticJobRepository implements JobRepository {
|
|||
solanaSlotRepo: SolanaSlotRepository;
|
||||
}
|
||||
) {
|
||||
this.fileRepo = new FileMetadataRepo(path);
|
||||
this.fileRepo = new FileMetadataRepository(path);
|
||||
this.blockRepoProvider = blockRepoProvider;
|
||||
this.metadataRepo = repos.metadataRepo;
|
||||
this.statsRepo = repos.statsRepo;
|
||||
|
|
|
@ -4,11 +4,11 @@ import {
|
|||
PublicKey,
|
||||
VersionedTransactionResponse,
|
||||
SolanaJSONRPCError,
|
||||
Finality,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
import { Fallible, solana } from "../../domain/entities";
|
||||
import { solana } from "../../domain/entities";
|
||||
import { SolanaSlotRepository } from "../../domain/repositories";
|
||||
import { Fallible, SolanaFailure } from "../../domain/errors";
|
||||
|
||||
export class Web3SolanaSlotRepository implements SolanaSlotRepository {
|
||||
connection: Connection;
|
||||
|
@ -21,7 +21,7 @@ export class Web3SolanaSlotRepository implements SolanaSlotRepository {
|
|||
return this.connection.getSlot(commitment as Commitment);
|
||||
}
|
||||
|
||||
getBlock(slot: number, finality?: string): Promise<Fallible<solana.Block, solana.Failure>> {
|
||||
getBlock(slot: number, finality?: string): Promise<Fallible<solana.Block, SolanaFailure>> {
|
||||
return this.connection
|
||||
.getBlock(slot, {
|
||||
maxSupportedTransactionVersion: 0,
|
||||
|
@ -29,21 +29,21 @@ export class Web3SolanaSlotRepository implements SolanaSlotRepository {
|
|||
})
|
||||
.then((block) => {
|
||||
if (block === null) {
|
||||
return Fallible.error<solana.Block, solana.Failure>(
|
||||
new solana.Failure(0, "Block not found")
|
||||
return Fallible.error<solana.Block, SolanaFailure>(
|
||||
new SolanaFailure(0, "Block not found")
|
||||
);
|
||||
}
|
||||
return Fallible.ok<solana.Block, solana.Failure>({
|
||||
return Fallible.ok<solana.Block, SolanaFailure>({
|
||||
...block,
|
||||
transactions: block.transactions.map((tx) => this.mapTx(tx, slot)),
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err instanceof SolanaJSONRPCError) {
|
||||
return Fallible.error(new solana.Failure(err.code, err.message));
|
||||
return Fallible.error(new SolanaFailure(err.code, err.message));
|
||||
}
|
||||
|
||||
return Fallible.error(new solana.Failure(0, err.message));
|
||||
return Fallible.error(new SolanaFailure(0, err.message));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ if (!("toJSON" in BigInt.prototype)) {
|
|||
});
|
||||
}
|
||||
|
||||
export * from "./FileMetadataRepo";
|
||||
export * from "./FileMetadataRepository";
|
||||
export * from "./SnsEventRepository";
|
||||
export * from "./EvmJsonRPCBlockRepository";
|
||||
export * from "./PromStatRepository";
|
||||
|
|
|
@ -10,7 +10,8 @@ import {
|
|||
StatRepository,
|
||||
} from "../../../../src/domain/repositories";
|
||||
import { thenWaitForAssertion } from "../../../wait-assertion";
|
||||
import { Fallible, solana } from "../../../../src/domain/entities";
|
||||
import { solana } from "../../../../src/domain/entities";
|
||||
import { Fallible } from "../../../../src/domain/errors";
|
||||
|
||||
let pollSolanaTransactions: PollSolanaTransactions;
|
||||
let cfg: PollSolanaTransactionsConfig;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { EvmJsonRPCBlockRepository } from "../../../src/infrastructure/repositor
|
|||
import axios from "axios";
|
||||
import nock from "nock";
|
||||
import { EvmLogFilter, EvmTag } from "../../../src/domain/entities";
|
||||
import { HttpClient } from "../../../src/infrastructure/repositories/HttpClient";
|
||||
import { HttpClient } from "../../../src/infrastructure/http/HttpClient";
|
||||
|
||||
axios.defaults.adapter = "http"; // needed by nock
|
||||
const rpc = "http://localhost";
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, expect, it, beforeEach, afterEach } from "@jest/globals";
|
||||
import fs from "fs";
|
||||
import { FileMetadataRepo } from "../../../src/infrastructure/repositories";
|
||||
import { FileMetadataRepository } from "../../../src/infrastructure/repositories";
|
||||
|
||||
describe("FileMetadataRepo", () => {
|
||||
describe("FileMetadataRepository", () => {
|
||||
const dirPath = "./metadata-repo";
|
||||
const repo = new FileMetadataRepo(dirPath);
|
||||
const repo = new FileMetadataRepository(dirPath);
|
||||
|
||||
beforeEach(() => {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
|
|
Loading…
Reference in New Issue