Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(database): split storage to commits, blocks and transactions #794

Merged
merged 21 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/api-development/source/controllers/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,6 @@ export class BlocksController extends Controller {

// TODO: Support height only
private async getBlock(idOrHeight: string): Promise<Contracts.Crypto.Block | undefined> {
return (await this.database.getCommit(Number.parseInt(idOrHeight)))?.block;
return await this.database.getBlock(Number.parseInt(idOrHeight));
}
}
6 changes: 3 additions & 3 deletions packages/api-evm/source/actions/eth-get-block-by-hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ export class EthGetBlockByHashAction implements Contracts.Api.RPC.Action {
public async handle(parameters: [string, boolean]): Promise<object | null> {
const transactionObject = parameters[1];

const commit = await this.databaseService.getCommitById(parameters[0].slice(2));
const block = await this.databaseService.getBlockById(parameters[0].slice(2));

if (!commit) {
if (!block) {
// eslint-disable-next-line unicorn/no-null
return null;
}

return this.app.resolve(BlockResource).transform(commit.block, transactionObject);
return this.app.resolve(BlockResource).transform(block, transactionObject);
}
}
6 changes: 3 additions & 3 deletions packages/api-evm/source/actions/eth-get-block-by-number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ export class EthGetBlockByNumberAction implements Contracts.Api.RPC.Action {
const height = parameters[0].startsWith("0x") ? Number.parseInt(parameters[0]) : this.stateStore.getHeight();
const transactionObject = parameters[1];

const commit = await this.databaseService.getCommit(height);
const block = await this.databaseService.getBlock(height);

if (!commit) {
if (!block) {
// eslint-disable-next-line unicorn/no-null
return null;
}

return this.app.resolve(BlockResource).transform(commit.block, transactionObject);
return this.app.resolve(BlockResource).transform(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
Expand Up @@ -12,5 +12,6 @@ export * from "./eth-get-uncle-count-by-block-hash.js";
export * from "./eth-get-uncle-count-by-block-number.js";
export * from "./net-listening.js";
export * from "./net-peer-count.js";
export * from "./net-version.js";
export * from "./web3-client-version.js";
export * from "./web3-sha3.js";
46 changes: 46 additions & 0 deletions packages/api-evm/source/actions/net-version.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Identifiers } from "@mainsail/contracts";
import { Validator } from "@mainsail/validation";

import { describe, Sandbox } from "../../../test-framework/source";
import { NetVersion } from "./index.js";

describe<{
sandbox: Sandbox;
action: NetVersion;
validator: Validator;
}>("NetVersion", ({ beforeEach, it, assert }) => {
const version = "0.0.1";

beforeEach(async (context) => {
context.sandbox = new Sandbox();

context.sandbox.app.bind(Identifiers.Cryptography.Configuration).toConstantValue({
get: () => "nethash",
});

context.action = context.sandbox.app.resolve(NetVersion);
context.validator = context.sandbox.app.resolve(Validator);
});

it("should have a name", ({ action }) => {
assert.equal(action.name, "net_version");
});

it("schema should be array with 0 parameters", ({ action, validator }) => {
assert.equal(action.schema, {
$id: "jsonRpc_net_version",
maxItems: 0,
type: "array",
});

validator.addSchema(action.schema);

assert.undefined(validator.validate("jsonRpc_net_version", []).errors);
assert.defined(validator.validate("jsonRpc_net_version", [1]).errors);
assert.defined(validator.validate("jsonRpc_net_version", {}).errors);
});

it("should return the web3 client version", async ({ action }) => {
assert.equal(await action.handle([]), `nethash`);
});
});
20 changes: 20 additions & 0 deletions packages/api-evm/source/actions/net-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { inject, injectable } from "@mainsail/container";
import { Contracts, Identifiers } from "@mainsail/contracts";

@injectable()
export class NetVersion implements Contracts.Api.RPC.Action {
@inject(Identifiers.Cryptography.Configuration)
private readonly configuration!: Contracts.Crypto.Configuration;

public readonly name: string = "net_version";

public readonly schema = {
$id: `jsonRpc_${this.name}`,
maxItems: 0,
type: "array",
};

public async handle(parameters: []): Promise<string> {
return this.configuration.get<string>("network.nethash");
}
}
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 @@ -17,6 +17,7 @@ import {
EthGetUncleCountByBlockNumber,
NetListeningAction,
NetPeerCountAction,
NetVersion,
Web3ClientVersionAction,
Web3Sha3,
} from "./actions/index.js";
Expand Down Expand Up @@ -83,6 +84,7 @@ export class ServiceProvider extends AbstractServiceProvider<Server> {
this.app.resolve(EthGetUncleCountByBlockNumber),
this.app.resolve(NetListeningAction),
this.app.resolve(NetPeerCountAction),
this.app.resolve(NetVersion),
this.app.resolve(Web3ClientVersionAction),
this.app.resolve(Web3Sha3),
];
Expand Down
4 changes: 2 additions & 2 deletions packages/api-sync/source/restore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ export class Restore {
let currentHeight = 0;

do {
const commits = await this.databaseService.findCommits(
const commits = this.databaseService.readCommits(
Math.min(currentHeight, mostRecentCommit.block.header.height),
Math.min(currentHeight + BATCH_SIZE, mostRecentCommit.block.header.height),
);

const blocks: Models.Block[] = [];
const transactions: Models.Transaction[] = [];

for (const { proof, block } of commits) {
for await (const { proof, block } of commits) {
blocks.push({
commitRound: proof.round,
generatorAddress: block.header.generatorAddress,
Expand Down
6 changes: 3 additions & 3 deletions packages/bootstrap/source/bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ export class Bootstrapper {
}

async #checkStoredGenesisCommit(): Promise<void> {
const genesisCommit = await this.databaseService.getCommit(0);
const genesisBlock = await this.databaseService.getBlock(0);

if (!genesisCommit) {
if (!genesisBlock) {
return;
}

if (this.stateStore.getGenesisCommit().block.data.id !== genesisCommit.block.data.id) {
if (this.stateStore.getGenesisCommit().block.data.id !== genesisBlock.data.id) {
throw new Error("Block from crypto.json doesn't match stored genesis block");
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/consensus-storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@mainsail/container": "workspace:*",
"@mainsail/contracts": "workspace:*",
"@mainsail/kernel": "workspace:*",
"lmdb": "3.1.4"
"lmdb": "3.2.0"
},
"devDependencies": {
"uvu": "^0.5.6"
Expand Down
2 changes: 0 additions & 2 deletions packages/contracts/source/contracts/crypto/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ export interface BlockFactory {
}

export interface BlockSerializer {
headerSize(): number;

totalSize(block: BlockDataSerializable): number;

serializeHeader(block: BlockDataSerializable): Promise<Buffer>;
Expand Down
2 changes: 0 additions & 2 deletions packages/contracts/source/contracts/crypto/commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ export interface CommitProof {
}

export interface CommitSerializer {
proofSize(): number;

serializeCommitProof(proof: CommitProof): Promise<Buffer>;

serializeCommit(commit: CommitSerializable): Promise<Buffer>;
Expand Down
15 changes: 11 additions & 4 deletions packages/contracts/source/contracts/database.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Block } from "./crypto/block.js";
import { Commit } from "./crypto/commit.js";
import { Block, BlockHeader, Commit, Transaction } from "./crypto/index.js";

export interface State {
height: number;
Expand All @@ -11,15 +10,23 @@ export interface DatabaseService {
isEmpty(): boolean;

getState(): State;

getCommit(height: number): Promise<Commit | undefined>;
getCommitById(id: string): Promise<Commit | undefined>;
getLastCommit(): Promise<Commit>;
hasCommitById(id: string): boolean;
findCommitBuffers(start: number, end: number): Promise<Buffer[]>;
readCommits(start: number, end: number): AsyncGenerator<Commit>;

getBlock(height: number): Promise<Block | undefined>;
getBlockById(id: string): Promise<Block | undefined>;
findBlocks(start: number, end: number): Promise<Block[]>;
findCommits(start: number, end: number): Promise<Commit[]>;

getLastCommit(): Promise<Commit>;
getBlockHeader(height: number): Promise<BlockHeader | undefined>;
getBlockHeaderById(id: string): Promise<BlockHeader | undefined>;

getTransactionById(id: string): Promise<Transaction | undefined>;

addCommit(block: Commit): void;
persist(): Promise<void>;
}
5 changes: 5 additions & 0 deletions packages/contracts/source/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@ export const Identifiers = {
Block: {
Deserializer: Symbol("Crypto<Block.Deserializer>"),
Factory: Symbol("Crypto<Block.Factory>"),
HeaderSize: Symbol("Crypto<Block.HeaderSize>"),
IDFactory: Symbol("Crypto<Block.IDFactory>"),
Serializer: Symbol("Crypto<Block.Serializer>"),
Verifier: Symbol("Crypto<Block.Verifier>"),
},
Commit: {
Deserializer: Symbol("Crypto<Commit.Deserializer>"),
Factory: Symbol("Crypto<Commit.Factory>"),
ProofSize: Symbol("Crypto<Commit.ProofSize>"),
Serializer: Symbol("Crypto<Commit.Serializer>"),
},
Configuration: Symbol("Crypto<Configuration>"),
Expand Down Expand Up @@ -130,7 +132,10 @@ export const Identifiers = {
Storage: {
Block: Symbol("Database<Storage.Block>"),
BlockId: Symbol("Database<Storage.BlockId>"),
Commit: Symbol("Database<Storage.Commit>"),
State: Symbol("Database<Storage.State>"),
Transaction: Symbol("Database<Storage.Transaction>"),
TransactionIds: Symbol("Database<Storage.TransactionIds>"),
},
},
Evm: {
Expand Down
6 changes: 3 additions & 3 deletions packages/crypto-block/source/deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export class Deserializer implements Contracts.Crypto.BlockDeserializer {
@inject(Identifiers.Cryptography.Serializer)
private readonly serializer!: Contracts.Serializer.Serializer;

@inject(Identifiers.Cryptography.Block.Serializer)
private readonly blockSerializer!: Contracts.Crypto.BlockSerializer;
@inject(Identifiers.Cryptography.Block.HeaderSize)
private readonly headerSize!: () => number;

public async deserializeHeader(serialized: Buffer): Promise<Contracts.Crypto.BlockHeader> {
const buffer: ByteBuffer = ByteBuffer.fromBuffer(serialized);
Expand Down Expand Up @@ -51,7 +51,7 @@ export class Deserializer implements Contracts.Crypto.BlockDeserializer {
const block = {} as Contracts.Crypto.BlockHeader;

await this.serializer.deserialize<Contracts.Crypto.BlockData>(buffer, block, {
length: this.blockSerializer.headerSize(),
length: this.headerSize(),
schema: {
version: {
type: "uint8",
Expand Down
22 changes: 22 additions & 0 deletions packages/crypto-block/source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ export * from "./verifier.js";

export class ServiceProvider extends Providers.ServiceProvider {
public async register(): Promise<void> {
this.app.bind(Identifiers.Cryptography.Block.HeaderSize).toFunction(() => {
const hashByteLength = this.app.get<number>(Identifiers.Cryptography.Hash.Size.SHA256);
const generatorAddressByteLength = this.app.get<number>(Identifiers.Cryptography.Identity.Address.Size);

return (
1 + // version
6 + // timestamp
4 + // height
4 + // round
hashByteLength + // previousBlock
hashByteLength + // stateHash
2 + // numberOfTransactions
4 + // totalGasUsed
32 + // totalAmount
32 + // totalFee
32 + // reward
4 + // payloadLength
hashByteLength + // payloadHash
generatorAddressByteLength
);
});

this.app.bind(Identifiers.Cryptography.Block.Deserializer).to(Deserializer).inSingletonScope();
this.app.bind(Identifiers.Cryptography.Block.Factory).to(BlockFactory).inSingletonScope();
this.app.bind(Identifiers.Cryptography.Block.IDFactory).to(IDFactory).inSingletonScope();
Expand Down
26 changes: 2 additions & 24 deletions packages/crypto-block/source/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,8 @@ export class Serializer implements Contracts.Crypto.BlockSerializer {
@tagged("type", "wallet")
private readonly serializer!: Contracts.Serializer.Serializer;

@inject(Identifiers.Cryptography.Hash.Size.SHA256)
private readonly hashByteLength!: number;

@inject(Identifiers.Cryptography.Identity.Address.Size)
private readonly generatorAddressByteLength!: number;

public headerSize(): number {
return (
1 + // version
6 + // timestamp
4 + // height
4 + // round
this.hashByteLength + // previousBlock
this.hashByteLength + // stateHash
2 + // numberOfTransactions
4 + // totalGasUsed
32 + // totalAmount
32 + // totalFee
32 + // reward
4 + // payloadLength
this.hashByteLength + // payloadHash
this.generatorAddressByteLength
);
}
@inject(Identifiers.Cryptography.Block.HeaderSize)
private readonly headerSize!: () => number;

public totalSize(block: Contracts.Crypto.BlockDataSerializable): number {
return this.headerSize() + block.payloadLength;
Expand Down
9 changes: 4 additions & 5 deletions packages/crypto-block/source/verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ export class Verifier implements Contracts.Crypto.BlockVerifier {
@inject(Identifiers.Cryptography.Configuration)
private readonly configuration!: Contracts.Crypto.Configuration;

@inject(Identifiers.Cryptography.Block.Serializer)
private readonly serializer!: Contracts.Crypto.BlockSerializer;

@inject(Identifiers.Cryptography.Hash.Factory)
private readonly hashFactory!: Contracts.Crypto.HashFactory;

@inject(Identifiers.Cryptography.Block.HeaderSize)
private readonly headerSize!: () => number;

public async verify(block: Contracts.Crypto.Block): Promise<Contracts.Crypto.BlockVerification> {
const blockData: Contracts.Crypto.BlockData = block.data;
const result: Utils.Mutable<Contracts.Crypto.BlockVerification> = {
Expand Down Expand Up @@ -45,8 +45,7 @@ export class Verifier implements Contracts.Crypto.BlockVerifier {
result.errors.push("Invalid block version");
}

const headerSize = this.serializer.headerSize();
const totalSize = headerSize + block.header.payloadLength;
const totalSize = this.headerSize() + block.header.payloadLength;
if (totalSize > constants.block.maxPayload) {
result.errors.push(`Payload is too large: ${totalSize} > ${constants.block.maxPayload}`);
}
Expand Down
24 changes: 24 additions & 0 deletions packages/crypto-block/test/helpers/prepare-sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ import { Serializer } from "../../source/serializer";
export const prepareSandbox = async (context) => {
context.sandbox = new Sandbox();

context.sandbox.app.bind(Identifiers.Cryptography.Block.HeaderSize).toFunction(() => {
const hashByteLength = context.sandbox.app.get<number>(Identifiers.Cryptography.Hash.Size.SHA256);
const generatorAddressByteLength = context.sandbox.app.get<number>(
Identifiers.Cryptography.Identity.Address.Size,
);

return (
1 + // version
6 + // timestamp
4 + // height
4 + // round
hashByteLength + // previousBlock
hashByteLength + // stateHash
2 + // numberOfTransactions
4 + // totalGasUsed
32 + // totalAmount
32 + // totalFee
32 + // reward
4 + // payloadLength
hashByteLength + // payloadHash
generatorAddressByteLength
);
});

context.sandbox.app.get<Contracts.Kernel.Repository>(Identifiers.Config.Repository).set("crypto", crypto);
context.sandbox.app.bind(Identifiers.Services.EventDispatcher.Service).toConstantValue({ dispatchSync: () => {} });
context.sandbox.app.bind(Identifiers.Services.Log.Service).toConstantValue({});
Expand Down
Loading
Loading