Skip to content

Commit

Permalink
feat(evm-api): implement eth_getBlockByHash (#783)
Browse files Browse the repository at this point in the history
* Add blockId storage

* Store block id

* Implement getCommitById

* Add cache

* Add eth_getBlockByHash

* Remove store
  • Loading branch information
sebastijankuzner authored Nov 28, 2024
1 parent 8304bda commit 1e710e2
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 10 deletions.
38 changes: 38 additions & 0 deletions packages/api-evm/source/actions/eth-get-block-by-hash.ts
Original file line number Diff line number Diff line change
@@ -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<object | null> {
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);
}
}
1 change: 1 addition & 0 deletions packages/api-evm/source/actions/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
2 changes: 2 additions & 0 deletions packages/api-evm/source/service-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CallAction,
EthBlockNumberAction,
EthGetBalanceAction,
EthGetBlockByHashAction,
EthGetBlockByNumberAction,
EthGetCodeAction,
EthGetStorageAtAction,
Expand Down Expand Up @@ -66,6 +67,7 @@ export class ServiceProvider extends AbstractServiceProvider<Server> {
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),
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/source/contracts/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface DatabaseService {

getState(): State;
getCommit(height: number): Promise<Commit | undefined>;
getCommitById(id: string): Promise<Commit | undefined>;
findCommitBuffers(start: number, end: number): Promise<Buffer[]>;
readCommits(start: number, end: number): AsyncGenerator<Commit>;
findBlocks(start: number, end: number): Promise<Block[]>;
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/source/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const Identifiers = {
Service: Symbol("Database<Service>"),
Storage: {
Block: Symbol("Database<Storage.Block>"),
BlockId: Symbol("Database<Storage.BlockId>"),
State: Symbol("Database<Storage.State>"),
},
},
Expand Down
49 changes: 39 additions & 10 deletions packages/database/source/database-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number, Contracts.Crypto.Commit>();
#blockCache = new Map<number, Contracts.Crypto.Commit>();
#blockIdCache = new Map<string, number>();
#state = { height: 0, totalRound: 0 };

public async initialize(): Promise<void> {
Expand All @@ -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<Contracts.Crypto.Commit | undefined> {
Expand All @@ -48,6 +52,21 @@ export class DatabaseService implements Contracts.Database.DatabaseService {
return undefined;
}

public async getCommitById(id: string): Promise<Contracts.Crypto.Commit | undefined> {
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<Buffer[]> {
const heights: number[] = [];

Expand Down Expand Up @@ -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(
Expand All @@ -108,34 +127,44 @@ 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;
}

async persist(): Promise<void> {
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);
});

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<T>(data: unknown[], callback: (...arguments_: any[]) => Promise<T>): Promise<T[]> {
const result: T[] = [];

Expand Down
1 change: 1 addition & 0 deletions packages/database/source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" }));
}
}

0 comments on commit 1e710e2

Please sign in to comment.