From 1e710e27526f61d9c425bed0fdc32c57de6ee2da Mon Sep 17 00:00:00 2001 From: "Sebastijan K." <58827427+sebastijankuzner@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:37:31 +0100 Subject: [PATCH] feat(evm-api): implement `eth_getBlockByHash` (#783) * Add blockId storage * Store block id * Implement getCommitById * Add cache * Add eth_getBlockByHash * Remove store --- .../source/actions/eth-get-block-by-hash.ts | 38 ++++++++++++++ packages/api-evm/source/actions/index.ts | 1 + packages/api-evm/source/service-provider.ts | 2 + .../contracts/source/contracts/database.ts | 1 + packages/contracts/source/identifiers.ts | 1 + packages/database/source/database-service.ts | 49 +++++++++++++++---- packages/database/source/index.ts | 1 + 7 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 packages/api-evm/source/actions/eth-get-block-by-hash.ts diff --git a/packages/api-evm/source/actions/eth-get-block-by-hash.ts b/packages/api-evm/source/actions/eth-get-block-by-hash.ts new file mode 100644 index 000000000..fc1be3c19 --- /dev/null +++ b/packages/api-evm/source/actions/eth-get-block-by-hash.ts @@ -0,0 +1,38 @@ +import { inject, injectable } from "@mainsail/container"; +import { Contracts, Identifiers } from "@mainsail/contracts"; + +import { BlockResource } from "../resources/index.js"; + +@injectable() +export class EthGetBlockByHashAction implements Contracts.Api.RPC.Action { + public readonly name: string = "eth_getBlockByHash"; + + @inject(Identifiers.Application.Instance) + private readonly app!: Contracts.Kernel.Application; + + @inject(Identifiers.Database.Service) + private readonly databaseService!: Contracts.Database.DatabaseService; + + public readonly schema = { + $id: `jsonRpc_${this.name}`, + + maxItems: 2, + minItems: 2, + + prefixItems: [{ $ref: "prefixedHex" }, { type: "boolean" }], // TODO: Replace prefixedHex with prefixedBlockId + type: "array", + }; + + public async handle(parameters: [string, boolean]): Promise { + const transactionObject = parameters[1]; + + const commit = await this.databaseService.getCommitById(parameters[0].slice(2)); + + if (!commit) { + // eslint-disable-next-line unicorn/no-null + return null; + } + + return this.app.resolve(BlockResource).transform(commit.block, transactionObject); + } +} diff --git a/packages/api-evm/source/actions/index.ts b/packages/api-evm/source/actions/index.ts index 81419f858..c81b1f276 100644 --- a/packages/api-evm/source/actions/index.ts +++ b/packages/api-evm/source/actions/index.ts @@ -1,6 +1,7 @@ export * from "./eth-block-number.js"; export * from "./eth-call.js"; export * from "./eth-get-balance.js"; +export * from "./eth-get-block-by-hash.js"; export * from "./eth-get-block-by-number.js"; export * from "./eth-get-code.js"; export * from "./eth-get-storage-at.js"; diff --git a/packages/api-evm/source/service-provider.ts b/packages/api-evm/source/service-provider.ts index 3c0171dc4..7ff0734f0 100644 --- a/packages/api-evm/source/service-provider.ts +++ b/packages/api-evm/source/service-provider.ts @@ -6,6 +6,7 @@ import { CallAction, EthBlockNumberAction, EthGetBalanceAction, + EthGetBlockByHashAction, EthGetBlockByNumberAction, EthGetCodeAction, EthGetStorageAtAction, @@ -66,6 +67,7 @@ export class ServiceProvider extends AbstractServiceProvider { this.app.resolve(CallAction), this.app.resolve(EthBlockNumberAction), this.app.resolve(EthGetBalanceAction), + this.app.resolve(EthGetBlockByHashAction), this.app.resolve(EthGetBlockByNumberAction), this.app.resolve(EthGetCodeAction), this.app.resolve(EthGetStorageAtAction), diff --git a/packages/contracts/source/contracts/database.ts b/packages/contracts/source/contracts/database.ts index 610de4350..a7bb9e1cd 100644 --- a/packages/contracts/source/contracts/database.ts +++ b/packages/contracts/source/contracts/database.ts @@ -12,6 +12,7 @@ export interface DatabaseService { getState(): State; getCommit(height: number): Promise; + getCommitById(id: string): Promise; findCommitBuffers(start: number, end: number): Promise; readCommits(start: number, end: number): AsyncGenerator; findBlocks(start: number, end: number): Promise; diff --git a/packages/contracts/source/identifiers.ts b/packages/contracts/source/identifiers.ts index 0ab1010c4..cc880e376 100644 --- a/packages/contracts/source/identifiers.ts +++ b/packages/contracts/source/identifiers.ts @@ -129,6 +129,7 @@ export const Identifiers = { Service: Symbol("Database"), Storage: { Block: Symbol("Database"), + BlockId: Symbol("Database"), State: Symbol("Database"), }, }, diff --git a/packages/database/source/database-service.ts b/packages/database/source/database-service.ts index bb0bc7b8e..1a6688209 100644 --- a/packages/database/source/database-service.ts +++ b/packages/database/source/database-service.ts @@ -10,13 +10,17 @@ export class DatabaseService implements Contracts.Database.DatabaseService { @inject(Identifiers.Database.Storage.Block) private readonly blockStorage!: lmdb.Database; + @inject(Identifiers.Database.Storage.BlockId) + private readonly blockIdStorage!: lmdb.Database; + @inject(Identifiers.Database.Storage.State) private readonly stateStorage!: lmdb.Database; @inject(Identifiers.Cryptography.Commit.Factory) private readonly commitFactory!: Contracts.Crypto.CommitFactory; - #cache = new Map(); + #blockCache = new Map(); + #blockIdCache = new Map(); #state = { height: 0, totalRound: 0 }; public async initialize(): Promise { @@ -35,7 +39,7 @@ export class DatabaseService implements Contracts.Database.DatabaseService { } public isEmpty(): boolean { - return this.#cache.size === 0 && this.blockStorage.getKeysCount() === 0; + return this.#blockCache.size === 0 && this.blockStorage.getKeysCount() === 0; } public async getCommit(height: number): Promise { @@ -48,6 +52,21 @@ export class DatabaseService implements Contracts.Database.DatabaseService { return undefined; } + public async getCommitById(id: string): Promise { + const height = this.#getHeightById(id); + + if (height === undefined) { + return undefined; + } + + const bytes = this.#get(height); + if (bytes) { + return await this.commitFactory.fromBytes(bytes); + } + + return undefined; + } + public async findCommitBuffers(start: number, end: number): Promise { const heights: number[] = []; @@ -98,8 +117,8 @@ export class DatabaseService implements Contracts.Database.DatabaseService { throw new Error("Database is empty"); } - if (this.#cache.size > 0) { - return [...this.#cache.values()].pop()!; + if (this.#blockCache.size > 0) { + return [...this.#blockCache.values()].pop()!; } return await this.commitFactory.fromBytes( @@ -108,7 +127,8 @@ export class DatabaseService implements Contracts.Database.DatabaseService { } public addCommit(commit: Contracts.Crypto.Commit): void { - this.#cache.set(commit.block.data.height, commit); + this.#blockCache.set(commit.block.data.height, commit); + this.#blockIdCache.set(commit.block.data.id, commit.block.data.height); this.#state.height = commit.block.data.height; this.#state.totalRound += commit.proof.round + 1; @@ -116,8 +136,9 @@ export class DatabaseService implements Contracts.Database.DatabaseService { async persist(): Promise { await this.rootDb.transaction(() => { - for (const [height, commit] of this.#cache.entries()) { + for (const [height, commit] of this.#blockCache.entries()) { void this.blockStorage.put(height, Buffer.from(commit.serialized, "hex")); + void this.blockIdStorage.put(commit.block.data.id, height); } void this.stateStorage.put("state", this.#state); @@ -125,17 +146,25 @@ export class DatabaseService implements Contracts.Database.DatabaseService { await this.rootDb.flushed; - this.#cache.clear(); + this.#blockCache.clear(); } - #get(height: number): Buffer { - if (this.#cache.has(height)) { - return Buffer.from(this.#cache.get(height)!.serialized, "hex"); + #get(height: number): Buffer | undefined { + if (this.#blockCache.has(height)) { + return Buffer.from(this.#blockCache.get(height)!.serialized, "hex"); } return this.blockStorage.get(height); } + #getHeightById(id: string): number | undefined { + if (this.#blockIdCache.has(id)) { + return this.#blockIdCache.get(id); + } + + return this.blockIdStorage.get(id); + } + async #map(data: unknown[], callback: (...arguments_: any[]) => Promise): Promise { const result: T[] = []; diff --git a/packages/database/source/index.ts b/packages/database/source/index.ts index b17ad5f0d..26cddbc2e 100644 --- a/packages/database/source/index.ts +++ b/packages/database/source/index.ts @@ -33,6 +33,7 @@ export class ServiceProvider extends Providers.ServiceProvider { }); this.app.bind(Identifiers.Database.Root).toConstantValue(rootStorage); this.app.bind(Identifiers.Database.Storage.Block).toConstantValue(rootStorage.openDB({ name: "blocks" })); + this.app.bind(Identifiers.Database.Storage.BlockId).toConstantValue(rootStorage.openDB({ name: "block-id" })); this.app.bind(Identifiers.Database.Storage.State).toConstantValue(rootStorage.openDB({ name: "state" })); } }