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

Allow empty id and signature for constructor - Closes #995 #996

Merged
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
8 changes: 4 additions & 4 deletions packages/lisk-transactions/src/transaction_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ export interface TransactionJSON {
readonly amount: string;
readonly asset: TransactionAsset;
readonly fee: string;
readonly id: string;
readonly id?: string;
readonly recipientId: string;
readonly recipientPublicKey: string;
readonly senderId: string;
readonly recipientPublicKey?: string;
readonly senderId?: string;
readonly senderPublicKey: string;
readonly signature?: string;
readonly signatures: ReadonlyArray<string>;
readonly signSignature?: string;
readonly timestamp: number;
readonly type: number;
readonly receivedAt: Date;
readonly receivedAt?: Date;
}

export interface IsValidResponse {
Expand Down
146 changes: 81 additions & 65 deletions packages/lisk-transactions/src/transactions/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
// tslint:disable-next-line no-reference
/// <reference path="../../types/browserify-bignum/index.d.ts" />

import * as cryptography from '@liskhq/lisk-cryptography';
import {
bigNumberToBuffer,
getAddressFromPublicKey,
hexToBuffer,
} from '@liskhq/lisk-cryptography';
import BigNum from 'browserify-bignum';
import {
BYTESIZES,
Expand Down Expand Up @@ -60,20 +64,27 @@ export enum MultisignatureStatus {
export abstract class BaseTransaction {
public readonly amount: BigNum;
public readonly fee: BigNum;
public readonly id: string;
public readonly recipientId: string;
public readonly recipientPublicKey: string;
public readonly recipientPublicKey?: string;
public readonly senderId: string;
public readonly senderPublicKey: string;
public readonly signature: string = '';
public readonly signatures: ReadonlyArray<string> = [];
public readonly signSignature?: string;
public readonly timestamp: number;
public readonly type: number;
public readonly asset: TransactionAsset = {};
public readonly receivedAt: Date = new Date();
public readonly receivedAt: Date;
public readonly containsUniqueData?: boolean;
public isMultisignature?: MultisignatureStatus = MultisignatureStatus.UNKNOWN;
public isMultisignature: MultisignatureStatus = MultisignatureStatus.UNKNOWN;

private _id?: string;
private _signature?: string;
private _signSignature?: string;

public abstract assetToJSON(): TransactionAsset;
public abstract verifyAgainstOtherTransactions(
transactions: ReadonlyArray<TransactionJSON>,
): TransactionResponse;
protected abstract getAssetBytes(): Buffer;

public constructor(rawTransaction: TransactionJSON) {
const { valid, errors } = checkTypes(rawTransaction);
Expand All @@ -88,20 +99,40 @@ export abstract class BaseTransaction {
this.amount = new BigNum(rawTransaction.amount);
this.asset = rawTransaction.asset;
this.fee = new BigNum(rawTransaction.fee);
this.id = rawTransaction.id;
this._id = rawTransaction.id;
this.recipientId = rawTransaction.recipientId;
this.recipientPublicKey = rawTransaction.recipientPublicKey;
this.senderId = rawTransaction.senderId;
this.senderId =
rawTransaction.senderId ||
getAddressFromPublicKey(rawTransaction.senderPublicKey);
this.senderPublicKey = rawTransaction.senderPublicKey;
this.signature = rawTransaction.signature as string;
this._signature = rawTransaction.signature;
this.signatures = rawTransaction.signatures;
this.signSignature = rawTransaction.signSignature;
this._signSignature = rawTransaction.signSignature;
this.timestamp = rawTransaction.timestamp;
this.type = rawTransaction.type;
this.receivedAt = rawTransaction.receivedAt;
this.receivedAt = rawTransaction.receivedAt || new Date();
}

public abstract assetToJSON(): TransactionAsset;
public get id(): string {
if (!this._id) {
throw new Error('id is requied to be set before use');
}

return this._id;
}

public get signature(): string {
if (!this._signature) {
throw new Error('signature is requied to be set before use');
}

return this._signature;
}

public get signSignature(): string | undefined {
return this._signSignature;
}

public toJSON(): TransactionJSON {
const transaction = {
Expand All @@ -124,59 +155,17 @@ export abstract class BaseTransaction {
return transaction;
}

protected abstract getAssetBytes(): Buffer;

protected getBasicBytes(): Buffer {
const transactionType = Buffer.alloc(BYTESIZES.TYPE, this.type);
const transactionTimestamp = Buffer.alloc(BYTESIZES.TIMESTAMP);
transactionTimestamp.writeIntLE(this.timestamp, 0, BYTESIZES.TIMESTAMP);

const transactionSenderPublicKey = cryptography.hexToBuffer(
this.senderPublicKey,
);

const transactionRecipientID = this.recipientId
? cryptography.bigNumberToBuffer(
this.recipientId.slice(0, -1),
BYTESIZES.RECIPIENT_ID,
)
: Buffer.alloc(BYTESIZES.RECIPIENT_ID);

const transactionAmount = this.amount.toBuffer({
endian: 'little',
size: BYTESIZES.AMOUNT,
});

const transactionAsset =
this.asset && Object.keys(this.asset).length
? this.getAssetBytes()
: Buffer.alloc(0);

return Buffer.concat([
transactionType,
transactionTimestamp,
transactionSenderPublicKey,
transactionRecipientID,
transactionAmount,
transactionAsset,
]);
}

public getBytes(): Buffer {
const transactionBytes = Buffer.concat([
this.getBasicBytes(),
this.signature
? cryptography.hexToBuffer(this.signature)
: Buffer.alloc(0),
this.signSignature
? cryptography.hexToBuffer(this.signSignature)
: Buffer.alloc(0),
this._signature ? hexToBuffer(this._signature) : Buffer.alloc(0),
this._signSignature ? hexToBuffer(this._signSignature) : Buffer.alloc(0),
]);

return transactionBytes;
}

public checkSchema(): TransactionResponse {
public validateSchema(): TransactionResponse {
const transaction = this.toJSON();
const baseTransactionValidator = validator.compile(schemas.baseTransaction);
const valid = baseTransactionValidator(transaction) as boolean;
Expand All @@ -195,7 +184,7 @@ export abstract class BaseTransaction {
// `senderPublicKey` passed format check, safely check equality to senderId
if (
this.senderId.toUpperCase() !==
cryptography.getAddressFromPublicKey(this.senderPublicKey).toUpperCase()
getAddressFromPublicKey(this.senderPublicKey).toUpperCase()
) {
errors.push(
new TransactionError(
Expand Down Expand Up @@ -263,7 +252,7 @@ export abstract class BaseTransaction {

public getRequiredAttributes(): Attributes {
return {
ACCOUNTS: [cryptography.getAddressFromPublicKey(this.senderPublicKey)],
ACCOUNTS: [getAddressFromPublicKey(this.senderPublicKey)],
};
}

Expand Down Expand Up @@ -323,7 +312,7 @@ export abstract class BaseTransaction {
} else {
const transactionBytes = Buffer.concat([
this.getBasicBytes(),
cryptography.hexToBuffer(this.signature),
hexToBuffer(this.signature),
]);

const {
Expand Down Expand Up @@ -386,10 +375,6 @@ export abstract class BaseTransaction {
};
}

public abstract verifyAgainstOtherTransactions(
transactions: ReadonlyArray<TransactionJSON>,
): TransactionResponse;

public apply(sender: Account): TransactionResponse {
const updatedBalance = new BigNum(sender.balance).sub(this.fee);
const updatedAccount = { ...sender, balance: updatedBalance.toString() };
Expand Down Expand Up @@ -441,4 +426,35 @@ export abstract class BaseTransaction {

return timeElapsed > timeOut;
}

protected getBasicBytes(): Buffer {
const transactionType = Buffer.alloc(BYTESIZES.TYPE, this.type);
const transactionTimestamp = Buffer.alloc(BYTESIZES.TIMESTAMP);
transactionTimestamp.writeIntLE(this.timestamp, 0, BYTESIZES.TIMESTAMP);

const transactionSenderPublicKey = hexToBuffer(this.senderPublicKey);

const transactionRecipientID = this.recipientId
? bigNumberToBuffer(this.recipientId.slice(0, -1), BYTESIZES.RECIPIENT_ID)
: Buffer.alloc(BYTESIZES.RECIPIENT_ID);

const transactionAmount = this.amount.toBuffer({
endian: 'little',
size: BYTESIZES.AMOUNT,
});

const transactionAsset =
this.asset && Object.keys(this.asset).length
? this.getAssetBytes()
: Buffer.alloc(0);

return Buffer.concat([
transactionType,
transactionTimestamp,
transactionSenderPublicKey,
transactionRecipientID,
transactionAmount,
transactionAsset,
]);
}
}
3 changes: 0 additions & 3 deletions packages/lisk-transactions/src/utils/validation/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ export const transaction = {
$id: 'lisk/transaction',
type: 'object',
required: [
'id',
'type',
'amount',
'fee',
'senderPublicKey',
'senderId',
'recipientId',
'timestamp',
'asset',
'signature',
],
properties: {
id: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export const addTransactionFields = (transaction: any) => {
return {
...transaction,
signSignature: transaction.signSignature ? transaction.signSignature : undefined,
signSignature: transaction.signSignature
? transaction.signSignature
: undefined,
receivedAt: new Date(),
};
};
32 changes: 17 additions & 15 deletions packages/lisk-transactions/test/transactions/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,12 @@ describe('Base transaction class', () => {
});
});

describe('#checkSchema', () => {
describe('#validateSchema', () => {
it('should call toJSON', async () => {
const toJSONStub = sandbox
.stub(validTestTransaction, 'toJSON')
.returns({});
validTestTransaction.checkSchema();
validTestTransaction.validateSchema();

expect(toJSONStub).to.be.calledOnce;
});
Expand All @@ -337,7 +337,7 @@ describe('Base transaction class', () => {
const cryptographyGetAddressFromPublicKeyStub = sandbox
.stub(cryptography, 'getAddressFromPublicKey')
.returns('18278674964748191682L');
validTestTransaction.checkSchema();
validTestTransaction.validateSchema();

expect(
cryptographyGetAddressFromPublicKeyStub,
Expand All @@ -353,21 +353,21 @@ describe('Base transaction class', () => {
'hex',
),
);
validTestTransaction.checkSchema();
validTestTransaction.validateSchema();
expect(getBytesStub).to.be.calledOnce;
});

it('should call getId', async () => {
const getIdStub = sandbox
.stub(utils, 'getId')
.returns('15822870279184933850');
validTestTransaction.checkSchema();
validTestTransaction.validateSchema();

expect(getIdStub).to.be.calledOnce;
});

it('should return a successful transaction response with a valid transaction', async () => {
const { id, status, errors } = validTestTransaction.checkSchema();
const { id, status, errors } = validTestTransaction.validateSchema();

expect(id).to.be.eql(validTestTransaction.id);
expect(errors).to.be.eql([]);
Expand All @@ -390,7 +390,7 @@ describe('Base transaction class', () => {
const invalidTestTransaction = new TestTransaction(
invalidTransaction as any,
);
const { id, status, errors } = invalidTestTransaction.checkSchema();
const { id, status, errors } = invalidTestTransaction.validateSchema();

expect(id).to.be.eql(invalidTestTransaction.id);
(errors as ReadonlyArray<TransactionError>).forEach(error =>
Expand All @@ -411,7 +411,7 @@ describe('Base transaction class', () => {
id,
status,
errors,
} = invalidSenderIdTestTransaction.checkSchema();
} = invalidSenderIdTestTransaction.validateSchema();

expect(id).to.be.eql(invalidSenderIdTestTransaction.id);
expect((errors as ReadonlyArray<TransactionError>)[1])
Expand All @@ -432,7 +432,7 @@ describe('Base transaction class', () => {
const invalidIdTestTransaction = new TestTransaction(
invalidIdTransaction as any,
);
const { id, status, errors } = invalidIdTestTransaction.checkSchema();
const { id, status, errors } = invalidIdTestTransaction.validateSchema();

expect(id).to.be.eql(invalidIdTestTransaction.id);
expect((errors as ReadonlyArray<TransactionError>)[0])
Expand Down Expand Up @@ -546,7 +546,7 @@ describe('Base transaction class', () => {
const cryptographyGetAddressFromPublicKeyStub = sandbox
.stub(cryptography, 'getAddressFromPublicKey')
.returns('18278674964748191682L');
validTestTransaction.checkSchema();
validTestTransaction.validateSchema();

expect(
cryptographyGetAddressFromPublicKeyStub,
Expand Down Expand Up @@ -902,13 +902,15 @@ describe('Base transaction class', () => {
expect(state)
.to.be.an('object')
.and.to.have.property('sender');
expect((state as any).sender).to.have.property('balance', new BigNum(MAX_TRANSACTION_AMOUNT).add(validTestTransaction.fee).toString());
expect((state as any).sender).to.have.property(
'balance',
new BigNum(MAX_TRANSACTION_AMOUNT)
.add(validTestTransaction.fee)
.toString(),
);
expect((errors as ReadonlyArray<TransactionError>)[0])
.to.be.instanceof(TransactionError)
.and.to.have.property(
'message',
'Invalid balance amount',
);
.and.to.have.property('message', 'Invalid balance amount');
});
});

Expand Down
3 changes: 2 additions & 1 deletion packages/lisk-transactions/test/utils/sign_and_verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ describe('signAndVerify module', () => {
.stub(cryptography, 'hash')
.returns(
Buffer.from(
'62b13b81836f3f1e371eba2f7f8306ff23d00a87d9473793eda7f742f4cfc21c', 'hex'
'62b13b81836f3f1e371eba2f7f8306ff23d00a87d9473793eda7f742f4cfc21c',
'hex',
),
);

Expand Down
Loading