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

Update transaction and command verify step to have partial header - Closes #7603 #7606

Merged
merged 3 commits into from
Oct 5, 2022
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
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: {
ishantiw marked this conversation as resolved.
Show resolved Hide resolved
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(),
ishantiw marked this conversation as resolved.
Show resolved Hide resolved
});
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