Skip to content

Commit

Permalink
Switch to fs async api
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed May 27, 2022
1 parent c3f4669 commit a607841
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 29 deletions.
21 changes: 11 additions & 10 deletions packages/lodestar/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* @module chain
*/

import fs from "node:fs";
import path from "node:path";
import {
BeaconStateAllForks,
Expand All @@ -22,6 +21,7 @@ import {IBeaconDb} from "../db/index.js";
import {IMetrics} from "../metrics/index.js";
import {IEth1ForBlockProduction} from "../eth1/index.js";
import {IExecutionEngine} from "../executionEngine/index.js";
import {ensureDir, writeIfNotExist} from "../util/file.js";
import {CheckpointStateCache, StateContextCache} from "./stateCache/index.js";
import {BlockProcessor, PartiallyVerifiedBlockFlags} from "./blocks/index.js";
import {IBeaconClock, LocalClock} from "./clock/index.js";
Expand Down Expand Up @@ -305,17 +305,22 @@ export class BeaconChain implements IBeaconChain {

persistInvalidSszValue<T>(type: Type<T>, sszObject: T, suffix?: string): void {
if (this.opts.persistInvalidSszObjects) {
this.persistInvalidSszObject(type.typeName, type.serialize(sszObject), type.hashTreeRoot(sszObject), suffix);
void this.persistInvalidSszObject(type.typeName, type.serialize(sszObject), type.hashTreeRoot(sszObject), suffix);
}
}

persistInvalidSszView(view: TreeView<CompositeTypeAny>, suffix?: string): void {
if (this.opts.persistInvalidSszObjects) {
this.persistInvalidSszObject(view.type.typeName, view.serialize(), view.hashTreeRoot(), suffix);
void this.persistInvalidSszObject(view.type.typeName, view.serialize(), view.hashTreeRoot(), suffix);
}
}

private persistInvalidSszObject(typeName: string, bytes: Uint8Array, root: Uint8Array, suffix?: string): void {
private async persistInvalidSszObject(
typeName: string,
bytes: Uint8Array,
root: Uint8Array,
suffix?: string
): Promise<void> {
if (!this.opts.persistInvalidSszObjects) {
return;
}
Expand All @@ -328,15 +333,11 @@ export class BeaconChain implements IBeaconChain {
const dirpath = path.join(this.opts.persistInvalidSszObjectsDir ?? "invalid_ssz_objects", dateStr);
const filepath = path.join(dirpath, `${typeName}_${toHex(root)}.ssz`);

if (!fs.existsSync(dirpath)) {
fs.mkdirSync(dirpath, {recursive: true});
}
await ensureDir(dirpath);

// as of Feb 17 2022 there are a lot of duplicate files stored with different date suffixes
// remove date suffixes in file name, and check duplicate to avoid redundant persistence
if (!fs.existsSync(filepath)) {
fs.writeFileSync(filepath, bytes);
}
await writeIfNotExist(filepath, bytes);

this.logger.debug("Persisted invalid ssz object", {id: suffix, filepath});
}
Expand Down
30 changes: 20 additions & 10 deletions packages/lodestar/src/util/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@

import fs from "node:fs";
import path from "node:path";
import {promisify} from "node:util";

/**
* Recursively ensures directory exists by creating any missing directories
* @param {string} filePath
*/
export function ensureDirectoryExistence(filePath: string): boolean {
const dirname = path.dirname(filePath);
if (fs.existsSync(dirname)) {
/** Ensure a directory exists */
export async function ensureDir(path: string): Promise<void> {
try {
await promisify(fs.stat)(path);
} catch (_) {
// not exists
await promisify(fs.mkdir)(path, {recursive: true});
}
}

/** Write data to a file if it does not exist */
export async function writeIfNotExist(filepath: string, bytes: Uint8Array): Promise<boolean> {
try {
await promisify(fs.stat)(filepath);
return false;
// file exists, do nothing
} catch (_) {
// not exists
await promisify(fs.writeFile)(filepath, bytes);
return true;
}
ensureDirectoryExistence(dirname);
fs.mkdirSync(dirname);
return true;
}

export function rmDir(dir: string): void {
Expand Down
52 changes: 44 additions & 8 deletions packages/lodestar/test/unit/util/file.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
import {assert} from "chai";
import {ensureDirectoryExistence} from "../../../src/util/file.js";
import fs from "node:fs";
import path from "node:path";
import {expect} from "chai";
import {ensureDir, writeIfNotExist} from "../../../src/util/file.js";

describe("util/file", function () {
const testFilePath = "keys/toml/test_config.toml";
describe("file util", function () {
this.timeout(3000);
const dirPath = path.join(".", "keys/toml/test_config.toml");

it("should create directory needed for file writes", () => {
assert.isTrue(ensureDirectoryExistence(testFilePath));
describe("ensureDir", function () {
it("create dir if not exists", async () => {
expect(fs.existsSync(dirPath), `${dirPath} should not exist`).to.be.false;
await ensureDir(dirPath);
expect(fs.existsSync(dirPath), `${dirPath} should exist`).to.be.true;
fs.rmdirSync(dirPath);
});
});

it("should return true for existing directory", () => {
assert.isTrue(ensureDirectoryExistence("src"));
describe("writeIfNotExist", function () {
const filePath = path.join(dirPath, "test.txt");
const data = new Uint8Array([0, 1, 2]);
before(async () => {
await ensureDir(dirPath);
});

after(() => {
fs.rmdirSync(dirPath);
});

it("write to a non-existed file", async () => {
expect(fs.existsSync(filePath)).to.be.false;
expect(await writeIfNotExist(filePath, data)).to.be.true;
const bytes = fs.readFileSync(filePath);
expect(new Uint8Array(bytes)).to.be.deep.equals(data);

// clean up
fs.rmSync(filePath);
});

it("write to an existing file", async () => {
fs.writeFileSync(filePath, new Uint8Array([3, 4]));
expect(await writeIfNotExist(filePath, data)).to.be.false;
const bytes = fs.readFileSync(filePath);
expect(new Uint8Array(bytes)).not.to.be.deep.equals(data);

// clean up
fs.rmSync(filePath);
});
});
});
2 changes: 1 addition & 1 deletion packages/lodestar/test/utils/mocks/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class MockBeaconChain implements IBeaconChain {
this.reprocessController = new ReprocessController(null);
}

persistInvalidSszView(view: TreeView<CompositeTypeAny>, suffix?: string): void {}
persistInvalidSszView(_: TreeView<CompositeTypeAny>): void {}

getHeadState(): CachedBeaconStateAllForks {
return createCachedBeaconStateTest(this.state, this.config);
Expand Down

0 comments on commit a607841

Please sign in to comment.