Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Add ability to save inclusion proofs at every new block #9197

Merged
merged 8 commits into from
Mar 6, 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
6 changes: 6 additions & 0 deletions elements/lisk-chain/src/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as createDebug from 'debug';
import { regularMerkleTree } from '@liskhq/lisk-tree';
import {
DEFAULT_KEEP_EVENTS_FOR_HEIGHTS,
DEFAULT_KEEP_INCLUSION_PROOFS_FOR_HEIGHTS,
DEFAULT_MAX_BLOCK_HEADER_CACHE,
DEFAULT_MIN_BLOCK_HEADER_CACHE,
GENESIS_BLOCK_VERSION,
Expand All @@ -40,6 +41,7 @@ interface ChainConstructor {
readonly maxTransactionsSize: number;
readonly minBlockHeaderCache?: number;
readonly maxBlockHeaderCache?: number;
readonly keepInclusionProofsForHeights: number;
}

interface ChainInitArgs {
Expand All @@ -61,6 +63,7 @@ export class Chain {
readonly minBlockHeaderCache: number;
readonly maxBlockHeaderCache: number;
readonly keepEventsForHeights: number;
readonly keepInclusionProofsForHeights: number;
};

private _lastBlock?: Block;
Expand All @@ -74,6 +77,7 @@ export class Chain {
keepEventsForHeights = DEFAULT_KEEP_EVENTS_FOR_HEIGHTS,
minBlockHeaderCache = DEFAULT_MIN_BLOCK_HEADER_CACHE,
maxBlockHeaderCache = DEFAULT_MAX_BLOCK_HEADER_CACHE,
keepInclusionProofsForHeights = DEFAULT_KEEP_INCLUSION_PROOFS_FOR_HEIGHTS,
}: ChainConstructor) {
// Register codec schema
codec.addSchema(blockSchema);
Expand All @@ -86,6 +90,7 @@ export class Chain {
maxBlockHeaderCache,
minBlockHeaderCache,
keepEventsForHeights,
keepInclusionProofsForHeights,
};
}

Expand Down Expand Up @@ -118,6 +123,7 @@ export class Chain {
minBlockHeaderCache: this.constants.minBlockHeaderCache,
maxBlockHeaderCache: this.constants.maxBlockHeaderCache,
keepEventsForHeights: this.constants.keepEventsForHeights,
keepInclusionProofsForHeights: this.constants.keepInclusionProofsForHeights,
});
}

Expand Down
1 change: 1 addition & 0 deletions elements/lisk-chain/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import { utils } from '@liskhq/lisk-cryptography';

export const DEFAULT_KEEP_EVENTS_FOR_HEIGHTS = 300;
export const DEFAULT_KEEP_INCLUSION_PROOFS_FOR_HEIGHTS = 300;
export const DEFAULT_MIN_BLOCK_HEADER_CACHE = 309;
export const DEFAULT_MAX_BLOCK_HEADER_CACHE = 515;

Expand Down
16 changes: 14 additions & 2 deletions elements/lisk-chain/src/data_access/data_access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { Database, NotFoundError } from '@liskhq/lisk-db';
import { Database, NotFoundError, Proof } from '@liskhq/lisk-db';
import { Transaction } from '../transaction';
import { RawBlock } from '../types';
import { BlockHeader } from '../block_header';
Expand All @@ -29,6 +29,7 @@ interface DAConstructor {
readonly minBlockHeaderCache: number;
readonly maxBlockHeaderCache: number;
readonly keepEventsForHeights: number;
readonly keepInclusionProofsForHeights: number;
}

export class DataAccess {
Expand All @@ -40,8 +41,9 @@ export class DataAccess {
minBlockHeaderCache,
maxBlockHeaderCache,
keepEventsForHeights,
keepInclusionProofsForHeights,
}: DAConstructor) {
this._storage = new StorageAccess(db, { keepEventsForHeights });
this._storage = new StorageAccess(db, { keepEventsForHeights, keepInclusionProofsForHeights });
this._blocksCache = new BlockCache(minBlockHeaderCache, maxBlockHeaderCache);
}

Expand Down Expand Up @@ -243,6 +245,16 @@ export class DataAccess {
return events;
}

public async getInclusionProofs(height: number): Promise<Proof> {
const proofs = await this._storage.getInclusionProofs(height);

return proofs;
}

public async setInclusionProofs(proof: Proof, height: number): Promise<void> {
await this._storage.setInclusionProofs(proof, height);
}

public async isBlockPersisted(blockId: Buffer): Promise<boolean> {
const isPersisted = await this._storage.isBlockPersisted(blockId);

Expand Down
55 changes: 52 additions & 3 deletions elements/lisk-chain/src/data_access/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*
* Removal or modification of this copyright notice is prohibited.
*/
import { Batch, Database, NotFoundError } from '@liskhq/lisk-db';
import { Batch, Database, NotFoundError, Proof } from '@liskhq/lisk-db';
import { codec } from '@liskhq/lisk-codec';
import { utils } from '@liskhq/lisk-cryptography';
import { RawBlock, StateDiff } from '../types';
Expand All @@ -26,11 +26,16 @@ import {
DB_KEY_FINALIZED_HEIGHT,
DB_KEY_BLOCK_ASSETS_BLOCK_ID,
DB_KEY_BLOCK_EVENTS,
DB_KEY_INCLUSION_PROOFS,
} from '../db_keys';
import { concatDBKeys, uint32BE } from '../utils';
import { stateDiffSchema } from '../schema';
import { inclusionProofSchema, stateDiffSchema } from '../schema';
import { CurrentState } from '../state_store';
import { DEFAULT_KEEP_EVENTS_FOR_HEIGHTS, MAX_UINT32 } from '../constants';
import {
DEFAULT_KEEP_EVENTS_FOR_HEIGHTS,
DEFAULT_KEEP_INCLUSION_PROOFS_FOR_HEIGHTS,
MAX_UINT32,
} from '../constants';

const bytesArraySchema = {
$id: '/liskChain/bytesarray',
Expand Down Expand Up @@ -61,15 +66,19 @@ export const encodeByteArray = (val: Buffer[]): Buffer =>

interface StorageOption {
keepEventsForHeights?: number;
keepInclusionProofsForHeights?: number;
}

export class Storage {
private readonly _db: Database;
private readonly _keepEventsForHeights: number;
private readonly _keepInclusionProofsForHeights: number;

public constructor(db: Database, options?: StorageOption) {
this._db = db;
this._keepEventsForHeights = options?.keepEventsForHeights ?? DEFAULT_KEEP_EVENTS_FOR_HEIGHTS;
this._keepInclusionProofsForHeights =
options?.keepInclusionProofsForHeights ?? DEFAULT_KEEP_INCLUSION_PROOFS_FOR_HEIGHTS;
}

/*
Expand Down Expand Up @@ -257,6 +266,29 @@ export class Storage {
}
}

public async setInclusionProofs(proof: Proof, height: number): Promise<void> {
const proofBytes = codec.encode(inclusionProofSchema, proof);
await this._db.set(concatDBKeys(DB_KEY_INCLUSION_PROOFS, uint32BE(height)), proofBytes);
}

public async getInclusionProofs(height: number): Promise<Proof> {
try {
const proofBytes = await this._db.get(
concatDBKeys(DB_KEY_INCLUSION_PROOFS, uint32BE(height)),
);

return codec.decode(inclusionProofSchema, proofBytes);
} catch (error) {
if (!(error instanceof NotFoundError)) {
throw error;
}
return {
queries: [],
siblingHashes: [],
};
}
}

public async getTempBlocks(): Promise<Buffer[]> {
const stream = this._db.createReadStream({
gte: concatDBKeys(DB_KEY_TEMPBLOCKS_HEIGHT, uint32BE(0)),
Expand Down Expand Up @@ -419,6 +451,7 @@ export class Storage {
batch.del(concatDBKeys(DB_KEY_TRANSACTIONS_BLOCK_ID, id));
}
batch.del(concatDBKeys(DB_KEY_BLOCK_EVENTS, heightBuf));
batch.del(concatDBKeys(DB_KEY_INCLUSION_PROOFS, heightBuf));
if (assets.length > 0) {
batch.del(concatDBKeys(DB_KEY_BLOCK_ASSETS_BLOCK_ID, id));
}
Expand Down Expand Up @@ -486,6 +519,22 @@ export class Storage {
);
}
}

if (this._keepInclusionProofsForHeights > -1) {
Incede marked this conversation as resolved.
Show resolved Hide resolved
// inclusion proofs are removed only if finalized and below height - keepInclusionProofsForHeights
const minInclusionProofDeleteHeight = Math.min(
finalizedHeight,
Math.max(0, currentHeight - this._keepInclusionProofsForHeights),
);
if (minInclusionProofDeleteHeight > 0) {
const endHeight = Buffer.alloc(4);
endHeight.writeUInt32BE(minInclusionProofDeleteHeight - 1, 0);
await this._clear(
Buffer.concat([DB_KEY_INCLUSION_PROOFS, Buffer.alloc(4, 0)]),
Buffer.concat([DB_KEY_INCLUSION_PROOFS, endHeight]),
);
}
}
}

private async _getBlockAssets(blockID: Buffer): Promise<Buffer[]> {
Expand Down
1 change: 1 addition & 0 deletions elements/lisk-chain/src/db_keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const DB_KEY_TRANSACTIONS_ID = Buffer.from([6]);
export const DB_KEY_TEMPBLOCKS_HEIGHT = Buffer.from([7]);
export const DB_KEY_BLOCK_ASSETS_BLOCK_ID = Buffer.from([8]);
export const DB_KEY_BLOCK_EVENTS = Buffer.from([9]);
export const DB_KEY_INCLUSION_PROOFS = Buffer.from([11]);

export const DB_KEY_STATE_STORE = Buffer.from([10]);

Expand Down
36 changes: 36 additions & 0 deletions elements/lisk-chain/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,42 @@ export const blockAssetSchema = {
},
};

export const inclusionProofSchema = {
$id: '/storage/inclusionProof',
type: 'object',
required: ['siblingHashes', 'queries'],
properties: {
siblingHashes: {
type: 'array',
fieldNumber: 1,
items: {
dataType: 'bytes',
},
},
queries: {
type: 'array',
fieldNumber: 2,
items: {
type: 'object',
properties: {
key: {
dataType: 'bytes',
fieldNumber: 1,
},
value: {
dataType: 'bytes',
fieldNumber: 2,
},
bitmap: {
dataType: 'bytes',
fieldNumber: 3,
},
},
},
},
},
};

export const stateDiffSchema = {
$id: '/state/diff',
type: 'object',
Expand Down
5 changes: 5 additions & 0 deletions elements/lisk-chain/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export interface UpdatedDiff {
readonly value: Buffer;
}

export interface InclusionProofConfig {
keysForInclusionProof: Buffer[];
keepInclusionProofsForHeights: number;
}

type Primitive = string | number | bigint | boolean | null | undefined;
type Replaced<T, TReplace, TWith, TKeep = Primitive> = T extends TReplace | TKeep
? T extends TReplace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('dataAccess.blocks', () => {
minBlockHeaderCache: 3,
maxBlockHeaderCache: 5,
keepEventsForHeights: -1,
keepInclusionProofsForHeights: -1,
});
// Prepare sample data
const block300 = await createValidDefaultBlock({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('dataAccess.transactions', () => {
minBlockHeaderCache: 3,
maxBlockHeaderCache: 5,
keepEventsForHeights: -1,
keepInclusionProofsForHeights: -1,
});
});

Expand Down
1 change: 1 addition & 0 deletions elements/lisk-chain/test/unit/chain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('chain', () => {
const constants = {
maxTransactionsSize: 15 * 1024,
keepEventsForHeights: 300,
keepInclusionProofsForHeights: 300,
};
const emptyEncodedDiff = codec.encode(stateDiffSchema, {
created: [],
Expand Down
57 changes: 56 additions & 1 deletion elements/lisk-chain/test/unit/data_access/data_access.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
*/
import { Readable } from 'stream';
import { when } from 'jest-when';
import { NotFoundError, InMemoryDatabase } from '@liskhq/lisk-db';
import { NotFoundError, InMemoryDatabase, Proof } from '@liskhq/lisk-db';
import { codec } from '@liskhq/lisk-codec';
import { utils } from '@liskhq/lisk-cryptography';
import { DataAccess } from '../../../src/data_access';
import { createFakeBlockHeader, createValidDefaultBlock } from '../../utils/block';
Expand All @@ -24,11 +25,13 @@ import {
DB_KEY_BLOCKS_ID,
DB_KEY_TRANSACTIONS_ID,
DB_KEY_BLOCK_EVENTS,
DB_KEY_INCLUSION_PROOFS,
} from '../../../src/db_keys';
import { Block } from '../../../src/block';
import { Event } from '../../../src/event';
import { BlockAssets, BlockHeader } from '../../../src';
import { encodeByteArray } from '../../../src/data_access/storage';
import { inclusionProofSchema } from '../../../src/schema';

jest.mock('@liskhq/lisk-db');

Expand All @@ -45,6 +48,7 @@ describe('data_access', () => {
minBlockHeaderCache: 3,
maxBlockHeaderCache: 5,
keepEventsForHeights: 1,
keepInclusionProofsForHeights: 1,
});
block = await createValidDefaultBlock({ header: { height: 1 } });
});
Expand Down Expand Up @@ -400,6 +404,57 @@ describe('data_access', () => {
});
});

describe('#getInclusionProofs', () => {
it('should get empty array if the inclusionProofs does not exist', async () => {
db.get.mockRejectedValue(new NotFoundError());

const resp = await dataAccess.getInclusionProofs(30);
expect(db.get).toHaveBeenCalledWith(concatDBKeys(DB_KEY_INCLUSION_PROOFS, uint32BE(30)));
expect(resp).toEqual({
siblingHashes: [],
queries: [],
});
});

it('should get the inclusion proofs related to heights', async () => {
const original = {
siblingHashes: [Buffer.alloc(3)],
queries: [
{
key: Buffer.alloc(2),
value: Buffer.alloc(2),
bitmap: Buffer.alloc(1),
},
],
};
db.get.mockResolvedValue(codec.encode(inclusionProofSchema, original) as never);

const resp = await dataAccess.getInclusionProofs(30);
expect(db.get).toHaveBeenCalledWith(concatDBKeys(DB_KEY_INCLUSION_PROOFS, uint32BE(30)));
expect(resp).toEqual(original);
});
});

describe('#setInclusionProofs', () => {
it('should set inclusionProofs for a given height', async () => {
const proofs: Proof = {
siblingHashes: [Buffer.alloc(3)],
queries: [
{
key: Buffer.alloc(2),
value: Buffer.alloc(2),
bitmap: Buffer.alloc(1),
},
],
};
await dataAccess.setInclusionProofs(proofs, 30);
expect(db.set).toHaveBeenCalledWith(
concatDBKeys(DB_KEY_INCLUSION_PROOFS, uint32BE(30)),
codec.encode(inclusionProofSchema, proofs),
);
});
});

describe('#isBlockPersisted', () => {
it('should call check if the id exists in the database', async () => {
// Act
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"system": {
"dataPath": "~/.lisk/pos-mainchain-node-one",
"keepEventsForHeights": 300,
"logLevel": "info"
},
"rpc": {
Expand Down
Loading
Loading