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

Commit

Permalink
Merge pull request #7606 from LiskHQ/7603-update_verify_context
Browse files Browse the repository at this point in the history
Update transaction and command verify step to have partial header - Closes #7603
  • Loading branch information
shuse2 authored Oct 5, 2022
2 parents c930950 + 988a89f commit 7a1c539
Show file tree
Hide file tree
Showing 16 changed files with 47 additions and 11 deletions.
1 change: 1 addition & 0 deletions framework/src/abi/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export interface AfterTransactionsExecuteResponse {

export interface VerifyTransactionRequest {
contextID: Buffer;
header: BlockHeader;
transaction: Transaction;
}

Expand Down
6 changes: 5 additions & 1 deletion framework/src/abi/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ export const afterTransactionsExecuteResponseSchema = {
export const verifyTransactionRequestSchema = {
$id: '/abi/verifyTransactionRequest',
type: 'object',
required: ['contextID', 'transaction'],
required: ['contextID', 'transaction', 'header'],
properties: {
contextID: {
fieldNumber: 1,
Expand All @@ -377,6 +377,10 @@ export const verifyTransactionRequestSchema = {
fieldNumber: 2,
...transactionSchema,
},
header: {
fieldNumber: 3,
...blockHeaderSchema,
},
},
};

Expand Down
5 changes: 4 additions & 1 deletion framework/src/abi_handler/abi_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,18 +381,21 @@ export class ABIHandler implements ABI {
req: VerifyTransactionRequest,
): Promise<VerifyTransactionResponse> {
let stateStore: PrefixedStateReadWriter;
let header: BlockHeader;
if (!this._executionContext || !this._executionContext.id.equals(req.contextID)) {
stateStore = new PrefixedStateReadWriter(this._stateDB.newReadWriter());
header = new BlockHeader(req.header);
} else {
stateStore = this._executionContext.stateStore;
header = this._executionContext.header;
}
const context = new TransactionContext({
eventQueue: new EventQueue(0),
logger: this._logger,
transaction: new Transaction(req.transaction),
stateStore,
chainID: this.chainID,
// These values are not used
header,
currentValidators: [],
impliesMaxPrevote: true,
maxHeightCertified: 0,
Expand Down
1 change: 1 addition & 0 deletions framework/src/engine/consensus/consensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,7 @@ export class Consensus {
const { result: verifyResult } = await this._abi.verifyTransaction({
contextID,
transaction: transaction.toObject(),
header: block.header.toObject(),
});
if (verifyResult !== TransactionVerifyResult.OK) {
this._logger.debug(`Failed to verify transaction ${transaction.id.toString('hex')}`);
Expand Down
5 changes: 4 additions & 1 deletion framework/src/engine/endpoint/txpool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class TxpoolEndpoint {
const { result } = await this._abi.verifyTransaction({
contextID: Buffer.alloc(0),
transaction: transaction.toObject(),
header: this._chain.lastBlock.header.toObject(),
});
if (result === TransactionVerifyResult.INVALID) {
throw new InvalidTransactionError('Transaction verification failed.', transaction.id);
Expand Down Expand Up @@ -112,10 +113,12 @@ export class TxpoolEndpoint {

const req = ctx.params;
const transaction = Transaction.fromBytes(Buffer.from(req.transaction, 'hex'));
const header = this._chain.lastBlock.header.toObject();

const { result } = await this._abi.verifyTransaction({
contextID: Buffer.alloc(0),
transaction: transaction.toObject(),
header,
});
if (result === TransactionVerifyResult.INVALID) {
return {
Expand All @@ -135,7 +138,7 @@ export class TxpoolEndpoint {
transaction: transaction.toObject(),
assets: this._chain.lastBlock.assets.getAll(),
dryRun: true,
header: this._chain.lastBlock.header.toObject(),
header,
consensus,
});

Expand Down
2 changes: 2 additions & 0 deletions framework/src/engine/generator/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ export class Generator {
const { result: verifyResult } = await this._abi.verifyTransaction({
contextID: Buffer.alloc(0),
transaction,
header: this._chain.lastBlock.header.toObject(),
});
if (verifyResult !== TransactionVerifyResult.OK) {
throw new Error('Transaction is not valid');
Expand Down Expand Up @@ -721,6 +722,7 @@ export class Generator {
const { result: verifyResult } = await this._abi.verifyTransaction({
contextID,
transaction,
header: header.toObject(),
});
if (verifyResult !== TransactionVerifyResult.OK) {
throw new Error('Transaction is not valid');
Expand Down
1 change: 1 addition & 0 deletions framework/src/engine/generator/network_endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ export class NetworkEndpoint extends BaseNetworkEndpoint {
const { result } = await this._abi.verifyTransaction({
contextID: EMPTY_BUFFER,
transaction: transaction.toObject(),
header: this._chain.lastBlock.header.toObject(),
});
if (result === TransactionVerifyResult.INVALID) {
throw new InvalidTransactionError('Transaction verification failed.', transaction.id);
Expand Down
1 change: 1 addition & 0 deletions framework/src/engine/generator/strategies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export class HighFeeGenerationStrategy {
const { result: verifyResult } = await this._abi.verifyTransaction({
contextID,
transaction: lowestNonceHighestFeeTrx.toObject(),
header: header.toObject(),
});
if (verifyResult !== TransactionVerifyResult.OK) {
throw new Error('Transaction is not valid');
Expand Down
12 changes: 7 additions & 5 deletions framework/src/state_machine/transaction_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ interface ContextParams {
impliesMaxPrevote: boolean;
maxHeightCertified: number;
certificateThreshold: bigint;
header?: BlockHeader;
header: BlockHeader;
assets?: BlockAssets;
}

Expand All @@ -48,7 +48,7 @@ export class TransactionContext {
private readonly _logger: Logger;
private readonly _eventQueue: EventQueue;
private readonly _transaction: Transaction;
private readonly _header?: BlockHeader;
private readonly _header: BlockHeader;
private readonly _assets?: BlockAssets;
private readonly _currentValidators: Validator[];
private readonly _impliesMaxPrevote: boolean;
Expand All @@ -73,6 +73,7 @@ export class TransactionContext {
return {
logger: this._logger,
chainID: this._chainID,
header: { height: this._header.height, timestamp: this._header.timestamp },
getMethodContext: () => createImmutableMethodContext(this._stateStore),
getStore: (moduleID: Buffer, storePrefix: Buffer) =>
this._stateStore.getStore(moduleID, storePrefix),
Expand All @@ -81,9 +82,6 @@ export class TransactionContext {
}

public createTransactionExecuteContext(): TransactionExecuteContext {
if (!this._header) {
throw new Error('Transaction Execution requires block header in the context.');
}
if (!this._assets) {
throw new Error('Transaction Execution requires block assets in the context.');
}
Expand All @@ -110,6 +108,10 @@ export class TransactionContext {
return {
logger: this._logger,
chainID: this._chainID,
header: {
height: this._header.height,
timestamp: this._header.timestamp,
},
getMethodContext: () =>
createMethodContext({ stateStore: this._stateStore, eventQueue: this._eventQueue }),
getStore: (moduleID: Buffer, storePrefix: Buffer) =>
Expand Down
2 changes: 2 additions & 0 deletions framework/src/state_machine/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface VerificationResult {
export interface TransactionVerifyContext {
chainID: Buffer;
logger: Logger;
header: { timestamp: number; height: number };
transaction: Transaction;
getMethodContext: () => ImmutableMethodContext;
getStore: (moduleID: Buffer, storePrefix: Buffer) => ImmutableSubStore;
Expand All @@ -91,6 +92,7 @@ export interface TransactionVerifyContext {
export interface CommandVerifyContext<T = undefined> {
logger: Logger;
chainID: Buffer;
header: { timestamp: number; height: number };
transaction: Transaction; // without decoding params
params: T;
getMethodContext: () => ImmutableMethodContext;
Expand Down
11 changes: 9 additions & 2 deletions framework/test/unit/abi_handler/abi_handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,9 @@ describe('abi handler', () => {
describe('verifyTransaction', () => {
it('should execute verifyTransaction with existing context when context ID is not empty and resolve the response', async () => {
jest.spyOn(abiHandler['_stateMachine'], 'verifyTransaction');
const header = createFakeBlockHeader().toObject();
const { contextID } = await abiHandler.initStateMachine({
header: createFakeBlockHeader().toObject(),
header,
});
// Add random data to check if new state store is used or not
await abiHandler['_executionContext']?.stateStore.set(
Expand All @@ -445,6 +446,7 @@ describe('abi handler', () => {
const resp = await abiHandler.verifyTransaction({
contextID,
transaction: tx.toObject(),
header,
});

expect(abiHandler['_stateMachine'].verifyTransaction).toHaveBeenCalledTimes(1);
Expand All @@ -458,8 +460,9 @@ describe('abi handler', () => {

it('should execute verifyTransaction with new context when context ID is empty and resolve the response', async () => {
jest.spyOn(abiHandler['_stateMachine'], 'verifyTransaction');
const header = createFakeBlockHeader({ height: 10 }).toObject();
await abiHandler.initStateMachine({
header: createFakeBlockHeader().toObject(),
header,
});
// Add random data to check if new state store is used or not
const key = utils.getRandomBytes(20);
Expand All @@ -476,11 +479,15 @@ describe('abi handler', () => {
const resp = await abiHandler.verifyTransaction({
contextID: Buffer.alloc(0),
transaction: tx.toObject(),
header,
});

expect(abiHandler['_stateMachine'].verifyTransaction).toHaveBeenCalledTimes(1);
const usedStateStore = (abiHandler['_stateMachine'].verifyTransaction as jest.Mock).mock
.calls[0][0]['_stateStore'];
const usedHeader = (abiHandler['_stateMachine'].verifyTransaction as jest.Mock).mock
.calls[0][0]['_header'];
expect(usedHeader.height).toEqual(10);
// Expect used state store does not have previous information
await expect(usedStateStore.has(key)).resolves.toBeFalse();
expect(resp.result).toEqual(TransactionVerifyResult.INVALID);
Expand Down
5 changes: 5 additions & 0 deletions framework/test/unit/engine/generator/network_endpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ describe('generator network endpoint', () => {
constants: {
chainID: Buffer.from('chainID'),
},
lastBlock: {
header: {
toObject: jest.fn().mockReturnValue({}),
},
},
} as never;
pool = {
contains: jest.fn().mockReturnValue(false),
Expand Down
1 change: 1 addition & 0 deletions framework/test/unit/engine/generator/strategies.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const getTxMock = (
.calledWith({
contextID: expect.any(Buffer),
transaction: tx.toObject(),
header: expect.any(Object),
})
.mockResolvedValueOnce({
result: valid === false ? TransactionVerifyResult.PENDING : TransactionVerifyResult.OK,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ describe('CrossChainUpdateCommand', () => {
verifyContext = {
getMethodContext: () => createTransientMethodContext({ stateStore }),
getStore: createStoreGetter(stateStore).getStore,
header: { height: 20, timestamp: 10000 },
logger: testing.mocks.loggerMock,
chainID,
params,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ describe('CrossChainUpdateCommand', () => {
describe('verify', () => {
beforeEach(() => {
verifyContext = {
header: { height: 20, timestamp: 10000 },
getMethodContext: () => createTransientMethodContext({ stateStore }),
getStore: createStoreGetter(stateStore).getStore,
logger: testing.mocks.loggerMock,
Expand Down
3 changes: 2 additions & 1 deletion framework/test/unit/state_machine/state_machine.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { loggerMock } from '../../../src/testing/mocks';

describe('state_machine', () => {
const genesisHeader = {} as BlockHeader;
const header = {} as BlockHeader;
const header = { timestamp: 123, height: 20 } as BlockHeader;
const logger = {} as Logger;
const assets = new BlockAssets();
let stateStore: PrefixedStateReadWriter;
Expand Down Expand Up @@ -123,6 +123,7 @@ describe('state_machine', () => {
chainID,
logger,
transaction,
header,
getMethodContext: expect.any(Function),
getStore: expect.any(Function),
});
Expand Down

0 comments on commit 7a1c539

Please sign in to comment.