Skip to content

Commit

Permalink
fix(core-transaction-pool): use temporary wallets for transaction val…
Browse files Browse the repository at this point in the history
…idation (#2666)
  • Loading branch information
spkjp authored and faustbrian committed Jun 6, 2019
1 parent 2a38c9b commit 25783ae
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 11 deletions.
38 changes: 37 additions & 1 deletion __tests__/unit/core-transaction-pool/connection.forging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,16 @@ describe("Connection", () => {
const expectForgingTransactions = async (
transactions: Interfaces.ITransaction[],
countGood: number,
sliceBeginning?: boolean,
): Promise<string[]> => {
addTransactionsToMemory(transactions);

const forgingTransactions = await connection.getTransactionsForForging(100);
expect(forgingTransactions).toHaveLength(countGood);
expect(forgingTransactions).toEqual(
transactions.slice(transactions.length - countGood).map(({ serialized }) => serialized.toString("hex")),
transactions
.slice(sliceBeginning ? 0 : transactions.length - countGood, sliceBeginning ? countGood : undefined)
.map(({ serialized }) => serialized.toString("hex")),
);

return forgingTransactions;
Expand Down Expand Up @@ -180,6 +183,39 @@ describe("Connection", () => {
expect(spy).toHaveBeenCalled();
});

it("should remove multiple transactions of same sender that cannot be applied due to previous transaction", async () => {
const transactions = TransactionFactory.transfer()
.withPassphrase(delegates[20].passphrase)
.build(5);

const sender = databaseWalletManager.findByPublicKey(delegates[20].publicKey);
sender.balance = transactions[0].data.amount.plus(transactions[0].data.fee).times(3);

await expectForgingTransactions(transactions, 3, true);
});

it("should remove transactions that cannot be applied due to previous transaction", async () => {
const transactionA = TransactionFactory.transfer(delegates[21].address, 101 * 1e8)
.withPassphrase(delegates[20].passphrase)
.build(1)[0];

const transactionBs = TransactionFactory.transfer(delegates[20].address, 100 * 1e8)
.withPassphrase(delegates[21].passphrase)
.build(5);

const walletA = databaseWalletManager.findByPublicKey(delegates[20].publicKey);
walletA.balance = transactionA.data.amount.plus(transactionA.data.fee);

const walletB = databaseWalletManager.findByPublicKey(delegates[21].publicKey);
walletB.balance = Utils.BigNumber.ZERO;

await expectForgingTransactions([transactionBs[0], transactionA], 1);

memory.flush();

await expectForgingTransactions([transactionA, ...transactionBs], 2, true);
});

it("should remove transactions that have malformed bytes", async () => {
const malformedBytesFn = [
{ version: (b: ByteBuffer) => b.writeUint64(1111111) },
Expand Down
2 changes: 1 addition & 1 deletion __tests__/unit/core-transaction-pool/connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ describe("Connection", () => {
expect(getTransaction).toHaveBeenCalled();
expect(findByPublicKey).not.toHaveBeenCalled();
expect(canBeApplied).toHaveBeenCalled();
expect(applyToSenderInPool).not.toHaveBeenCalled();
expect(applyToSenderInPool).toHaveBeenCalled();
});

it("should not apply transaction to wallet if canBeApplied() failed", async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/core-transaction-pool/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"delay": "^4.2.0",
"fs-extra": "^8.0.1",
"lodash.camelcase": "^4.3.0",
"lodash.clonedeep": "^4.5.0",
"pluralize": "^7.0.0"
},
"devDependencies": {
Expand Down
60 changes: 51 additions & 9 deletions packages/core-transaction-pool/src/connection.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { strictEqual } from "assert";
import dayjs, { Dayjs } from "dayjs";
import clonedeep from "lodash.clonedeep";

import { app } from "@arkecosystem/core-container";
import { ApplicationEvents } from "@arkecosystem/core-event-emitter";
import { Database, EventEmitter, Logger, State, TransactionPool } from "@arkecosystem/core-interfaces";
import { Wallets } from "@arkecosystem/core-state";
import { Handlers } from "@arkecosystem/core-transactions";
import { Enums, Interfaces, Transactions, Utils } from "@arkecosystem/crypto";
import { strictEqual } from "assert";
import dayjs, { Dayjs } from "dayjs";
import { ITransactionsProcessed } from "./interfaces";
import { Memory } from "./memory";
import { Processor } from "./processor";
Expand Down Expand Up @@ -138,19 +141,19 @@ export class Connection implements TransactionPool.IConnection {
}

public async getTransactions(start: number, size: number, maxBytes?: number): Promise<Buffer[]> {
return (await this.getValidTransactions(start, size, maxBytes)).map(
return (await this.getValidatedTransactions(start, size, maxBytes)).map(
(transaction: Interfaces.ITransaction) => transaction.serialized,
);
}

public async getTransactionsForForging(blockSize: number): Promise<string[]> {
return (await this.getValidTransactions(0, blockSize, this.options.maxTransactionBytes)).map(transaction =>
return (await this.getValidatedTransactions(0, blockSize, this.options.maxTransactionBytes)).map(transaction =>
transaction.serialized.toString("hex"),
);
}

public async getTransactionIdsForForging(start: number, size: number): Promise<string[]> {
return (await this.getValidTransactions(start, size, this.options.maxTransactionBytes)).map(
return (await this.getValidatedTransactions(start, size, this.options.maxTransactionBytes)).map(
(transaction: Interfaces.ITransaction) => transaction.id,
);
}
Expand Down Expand Up @@ -364,7 +367,7 @@ export class Connection implements TransactionPool.IConnection {
return false;
}

private async getValidTransactions(
private async getValidatedTransactions(
start: number,
size: number,
maxBytes: number = 0,
Expand Down Expand Up @@ -481,6 +484,9 @@ export class Connection implements TransactionPool.IConnection {
(transaction: Interfaces.ITransaction) => !forgedIds.includes(transaction.id),
);

const databaseWalletManager: State.IWalletManager = this.databaseService.walletManager;
const localWalletManager: Wallets.WalletManager = new Wallets.WalletManager();

for (const transaction of unforgedTransactions) {
try {
const deserialized: Interfaces.ITransaction = Transactions.TransactionFactory.fromBytes(
Expand All @@ -489,9 +495,16 @@ export class Connection implements TransactionPool.IConnection {

strictEqual(transaction.id, deserialized.id);

const walletManager: State.IWalletManager = this.databaseService.walletManager;
const sender: State.IWallet = walletManager.findByPublicKey(transaction.data.senderPublicKey);
Handlers.Registry.get(transaction.type).canBeApplied(transaction, sender, walletManager);
const { sender, recipient } = this.getSenderAndRecipient(transaction, localWalletManager);

const handler: Handlers.TransactionHandler = Handlers.Registry.get(transaction.type);
handler.canBeApplied(transaction, sender, databaseWalletManager);

handler.applyToSenderInPool(transaction, localWalletManager);

if (recipient && sender.address !== recipient.address) {
handler.applyToRecipientInPool(transaction, localWalletManager);
}

validTransactions.push(deserialized.serialized.toString("hex"));
} catch (error) {
Expand All @@ -503,6 +516,35 @@ export class Connection implements TransactionPool.IConnection {
return validTransactions;
}

private getSenderAndRecipient(
transaction: Interfaces.ITransaction,
localWalletManager: State.IWalletManager,
): { sender: State.IWallet; recipient: State.IWallet } {
const databaseWalletManager: State.IWalletManager = this.databaseService.walletManager;
const { senderPublicKey, recipientId } = transaction.data;

let sender: State.IWallet;
let recipient: State.IWallet;

if (localWalletManager.hasByPublicKey(senderPublicKey)) {
sender = localWalletManager.findByPublicKey(senderPublicKey);
} else {
sender = clonedeep(databaseWalletManager.findByPublicKey(senderPublicKey));
localWalletManager.reindex(sender);
}

if (recipientId) {
if (localWalletManager.hasByAddress(recipientId)) {
recipient = localWalletManager.findByAddress(recipientId);
} else {
recipient = clonedeep(databaseWalletManager.findByAddress(recipientId));
localWalletManager.reindex(recipient);
}
}

return { sender, recipient };
}

private async removeForgedTransactions(transactions: Interfaces.ITransaction[]): Promise<string[]> {
const forgedIds: string[] = await this.databaseService.getForgedTransactionsIds(
transactions.map(({ id }) => id),
Expand Down

0 comments on commit 25783ae

Please sign in to comment.