Merge remote-tracking branch 'origin/feature/blockchain-watcher-solana-log' into feature/blockchain-watcher-solana-log

This commit is contained in:
matias martinez 2023-11-24 16:47:11 -03:00
commit 631d19cb65
19 changed files with 117 additions and 102 deletions

View File

@ -0,0 +1,11 @@
## Link Issue
<-- Link issue !-->
## Description
<-- Description of issue !-->
## Test or Screensshots
<-- How test this change !-->

View File

@ -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,
},
},
};

View File

@ -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",

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,2 @@
export * from "./SolanaFailure";
export * from "./Fallible";

View File

@ -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,

View File

@ -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");
}

View File

@ -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.

View File

@ -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) {

View File

@ -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;

View File

@ -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));
});
}

View File

@ -7,7 +7,7 @@ if (!("toJSON" in BigInt.prototype)) {
});
}
export * from "./FileMetadataRepo";
export * from "./FileMetadataRepository";
export * from "./SnsEventRepository";
export * from "./EvmJsonRPCBlockRepository";
export * from "./PromStatRepository";

View File

@ -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;

View File

@ -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";

View File

@ -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)) {