Skip to content

Commit

Permalink
feat(database): split storage to commits, blocks and transactions (#794)
Browse files Browse the repository at this point in the history
* Add net_version

* Register handler

* Return nethash

* Add test

* Add identifiers

* Open databases

* Extract ProofSize function

* Split commit and block storage

* Rename variable

* Extract headerSize

* Store transactions

* Remove findCommits

* Reorder

* Add getBlock & getBlockBy id

* Add getTransactionById

* Update lmdb version

* Update getBlockById

* Replace getCommit with get block

* Add getBlockHeader

* Fix typo

* Fix tests
  • Loading branch information
sebastijankuzner authored Dec 4, 2024
1 parent 7a9b32b commit 9c30dde
Show file tree
Hide file tree
Showing 26 changed files with 327 additions and 137 deletions.
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);
}
}
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
6 changes: 3 additions & 3 deletions packages/crypto-commit/source/deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ export class Deserializer implements Contracts.Crypto.CommitDeserializer {
@inject(Identifiers.Cryptography.Serializer)
private readonly serializer!: Contracts.Serializer.Serializer;

@inject(Identifiers.Cryptography.Commit.Serializer)
private readonly commitSerializer!: Contracts.Crypto.CommitSerializer;
@inject(Identifiers.Cryptography.Commit.ProofSize)
private readonly proofSize!: () => number;

public async deserializeCommitProof(serialized: Buffer): Promise<Contracts.Crypto.CommitProof> {
const buffer: ByteBuffer = ByteBuffer.fromBuffer(serialized);

const proof = {} as Contracts.Crypto.CommitProof;

await this.serializer.deserialize<Contracts.Crypto.CommitProof>(buffer, proof, {
length: this.commitSerializer.proofSize(),
length: this.proofSize(),
schema: {
round: {
type: "uint32",
Expand Down
8 changes: 4 additions & 4 deletions packages/crypto-commit/source/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ export class CommitFactory implements Contracts.Crypto.CommitFactory {
@inject(Identifiers.Cryptography.Block.Factory)
private readonly blockFactory!: Contracts.Crypto.BlockFactory;

@inject(Identifiers.Cryptography.Commit.Serializer)
private readonly commitSerializer!: Contracts.Crypto.CommitSerializer;

@inject(Identifiers.Cryptography.Commit.Deserializer)
private readonly commitDeserializer!: Contracts.Crypto.CommitDeserializer;

@inject(Identifiers.Cryptography.Commit.ProofSize)
private readonly proofSize!: () => number;

public async fromBytes(buff: Buffer): Promise<Contracts.Crypto.Commit> {
const buffer = ByteBuffer.fromBuffer(buff);

const proofBuffer = buffer.readBytes(this.commitSerializer.proofSize());
const proofBuffer = buffer.readBytes(this.proofSize());
const proof = await this.commitDeserializer.deserializeCommitProof(proofBuffer);

const block = await this.blockFactory.fromBytes(buffer.getRemainder());
Expand Down
8 changes: 8 additions & 0 deletions packages/crypto-commit/source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import { Serializer } from "./serializer.js";

export class ServiceProvider extends Providers.ServiceProvider {
public async register(): Promise<void> {
this.app.bind(Identifiers.Cryptography.Commit.ProofSize).toFunction(
() =>
4 + // round
this.app.getTagged<number>(Identifiers.Cryptography.Signature.Size, "type", "consensus") + // signature
1 +
8, // validator set bitmap);
);

this.app.bind(Identifiers.Cryptography.Commit.Serializer).to(Serializer).inSingletonScope();

this.app.bind(Identifiers.Cryptography.Commit.Deserializer).to(Deserializer).inSingletonScope();
Expand Down
11 changes: 2 additions & 9 deletions packages/crypto-commit/source/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,8 @@ export class Serializer implements Contracts.Crypto.CommitSerializer {
@inject(Identifiers.Cryptography.Serializer)
private readonly serializer!: Contracts.Serializer.Serializer;

@inject(Identifiers.Cryptography.Message.Serializer)
private readonly messageSerializer!: Contracts.Crypto.MessageSerializer;

public proofSize(): number {
return (
4 + // round
+this.messageSerializer.lockProofSize()
);
}
@inject(Identifiers.Cryptography.Commit.ProofSize)
private readonly proofSize!: () => number;

public async serializeCommitProof(commit: Contracts.Crypto.CommitProof): Promise<Buffer> {
return this.serializer.serialize<Contracts.Crypto.CommitProof>(commit, {
Expand Down
8 changes: 8 additions & 0 deletions packages/crypto-commit/test/helpers/prepare-sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ import { Serializer } from "../../source/serializer";
export const prepareSandbox = async (context) => {
context.sandbox = new Sandbox();

context.sandbox.app.bind(Identifiers.Cryptography.Commit.ProofSize).toFunction(
() =>
4 + // round
context.sandbox.app.getTagged<number>(Identifiers.Cryptography.Signature.Size, "type", "consensus") + // signature
1 +
8, // validator set bitmap);
);

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

0 comments on commit 9c30dde

Please sign in to comment.