Skip to content
20 changes: 20 additions & 0 deletions __tests__/unit/core-transactions/handlers/__support__/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,23 @@ export const buildMultiSignatureWallet = (): Wallets.Wallet => {

return wallet;
};

export const buildMultiSignatureRecipientWallet = (): Wallets.Wallet => {
const multiSignatureAsset: IMultiSignatureAsset = {
publicKeys: [
Identities.PublicKey.fromPassphrase(passphrases[1]),
Identities.PublicKey.fromPassphrase(passphrases[2]),
Identities.PublicKey.fromPassphrase(passphrases[3]),
],
min: 2,
};

const wallet = new Wallets.Wallet(
Identities.Address.fromMultiSignatureAsset(multiSignatureAsset),
new Services.Attributes.AttributeMap(getWalletAttributeSet()),
);
wallet.setPublicKey(Identities.PublicKey.fromMultiSignatureAsset(multiSignatureAsset));
wallet.setAttribute("multiSignature", multiSignatureAsset);

return wallet;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
HtlcLockExpiredError,
InsufficientBalanceError,
SentToBurnWalletError,
DisabledMultiSignatureSending,
} from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
Expand Down Expand Up @@ -233,12 +234,20 @@ describe("Htlc lock", () => {
).toResolve();
});

it("should not throw - multi sign", async () => {
it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureHtlcLockTransaction, multiSignatureWallet),
).toResolve();
});

it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
await expect(
handler.throwIfCannotBeApplied(multiSignatureHtlcLockTransaction, multiSignatureWallet),
).rejects.toThrow(DisabledMultiSignatureSending);
});

it("should throw if asset is undefined", async () => {
htlcLockTransaction.data.asset = undefined;

Expand Down
12 changes: 10 additions & 2 deletions __tests__/unit/core-transactions/handlers/two/ipfs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/fac
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { Mempool } from "@packages/core-transaction-pool";
import { MempoolIndexes } from "@packages/core-transactions/src/enums";
import { InsufficientBalanceError, IpfsHashAlreadyExists } from "@packages/core-transactions/src/errors";
import { InsufficientBalanceError, IpfsHashAlreadyExists, DisabledMultiSignatureSending } from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
Expand Down Expand Up @@ -309,12 +309,20 @@ describe("Ipfs", () => {
).toResolve();
});

it("should not throw - multi sign", async () => {
it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureIpfsTransaction, multiSignatureWallet),
).toResolve();
});

it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
await expect(
handler.throwIfCannotBeApplied(multiSignatureIpfsTransaction, multiSignatureWallet),
).rejects.toThrow(DisabledMultiSignatureSending);
});

it("should throw if wallet has insufficient funds", async () => {
senderWallet.setBalance(Utils.BigNumber.ZERO);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { StateStore } from "@packages/core-state/src/stores/state";
import { Generators } from "@packages/core-test-framework/src";
import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/factories";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { InsufficientBalanceError, SentToBurnWalletError } from "@packages/core-transactions/src/errors";
import { InsufficientBalanceError, DisabledMultiSignatureSending, DisabledMultiSignatureReceiving, SentToBurnWalletError } from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
Expand All @@ -18,6 +18,7 @@ import {
buildMultiSignatureWallet,
buildRecipientWallet,
buildSecondSignatureWallet,
buildMultiSignatureRecipientWallet,
buildSenderWallet,
initApp,
} from "../__support__/app";
Expand All @@ -26,6 +27,7 @@ let app: Application;
let senderWallet: Wallets.Wallet;
let secondSignatureWallet: Wallets.Wallet;
let multiSignatureWallet: Wallets.Wallet;
let multiSignatureRecipientWallet: Wallets.Wallet;
let recipientWallet: Wallets.Wallet;
let walletRepository: Contracts.State.WalletRepository;
let factoryBuilder: FactoryBuilder;
Expand Down Expand Up @@ -59,11 +61,13 @@ beforeEach(() => {
senderWallet = buildSenderWallet(factoryBuilder);
secondSignatureWallet = buildSecondSignatureWallet(factoryBuilder);
multiSignatureWallet = buildMultiSignatureWallet();
multiSignatureRecipientWallet = buildMultiSignatureRecipientWallet();
recipientWallet = buildRecipientWallet(factoryBuilder);

walletRepository.index(senderWallet);
walletRepository.index(secondSignatureWallet);
walletRepository.index(multiSignatureWallet);
walletRepository.index(multiSignatureRecipientWallet);
walletRepository.index(recipientWallet);
});

Expand Down Expand Up @@ -156,12 +160,22 @@ describe("MultiPaymentTransaction", () => {
).toResolve();
});

it("should not throw - multi sign", async () => {
it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureMultiPaymentTransaction, multiSignatureWallet),
).toResolve();
});

it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
await expect(
handler.throwIfCannotBeApplied(multiSignatureMultiPaymentTransaction, multiSignatureWallet),
).rejects.toThrow(
DisabledMultiSignatureSending,
);
});

it("should throw if asset is undefined", async () => {
multiPaymentTransaction.data.asset = undefined;

Expand Down Expand Up @@ -200,6 +214,34 @@ describe("MultiPaymentTransaction", () => {
);
});

it("should not throw if recipient is multisignature wallet and multiSignatureReceivingEnabled=true", async () => {
Managers.configManager.getMilestone().multiSignatureReceivingEnabled = true;
const multiPaymentTransaction = BuilderFactory.multiPayment()
.addPayment("ARYJmeYHSUTgbxaiqsgoPwf6M3CYukqdKN", "10")
.addPayment("AFyjB5jULQiYNsp37wwipCm9c7V1xEzTJD", "20")
.addPayment(multiSignatureRecipientWallet.getAddress(), "20")
.nonce("1")
.sign(passphrases[0])
.build();

await expect(handler.throwIfCannotBeApplied(multiPaymentTransaction, senderWallet)).toResolve();
});

it("should throw if recipient is multisignature wallet and multiSignatureReceivingEnabled=false", async () => {
Managers.configManager.getMilestone().multiSignatureReceivingEnabled = false;
const multiPaymentTransaction = BuilderFactory.multiPayment()
.addPayment("ARYJmeYHSUTgbxaiqsgoPwf6M3CYukqdKN", "10")
.addPayment("AFyjB5jULQiYNsp37wwipCm9c7V1xEzTJD", "20")
.addPayment(multiSignatureRecipientWallet.getAddress(), "20")
.nonce("1")
.sign(passphrases[0])
.build();

await expect(handler.throwIfCannotBeApplied(multiPaymentTransaction, senderWallet)).rejects.toThrow(
DisabledMultiSignatureReceiving,
);
});

it.each([false, undefined])(
"should not throw if recipient is burn wallet and blockBurnAddress is not set",
async (blockBurnAddress) => {
Expand Down
48 changes: 47 additions & 1 deletion __tests__/unit/core-transactions/handlers/two/transfer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
ColdWalletError,
InsufficientBalanceError,
SenderWalletMismatchError,
DisabledMultiSignatureReceiving,
DisabledMultiSignatureSending,
} from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
Expand All @@ -24,6 +26,7 @@ import {
buildMultiSignatureWallet,
buildRecipientWallet,
buildSecondSignatureWallet,
buildMultiSignatureRecipientWallet,
buildSenderWallet,
initApp,
} from "../__support__/app";
Expand All @@ -32,6 +35,7 @@ let app: Application;
let senderWallet: Wallets.Wallet;
let secondSignatureWallet: Wallets.Wallet;
let multiSignatureWallet: Wallets.Wallet;
let multiSignatureRecipientWallet: Wallets.Wallet;
let recipientWallet: Wallets.Wallet;
let walletRepository: Contracts.State.WalletRepository;
let factoryBuilder: FactoryBuilder;
Expand Down Expand Up @@ -59,11 +63,13 @@ beforeEach(() => {
senderWallet = buildSenderWallet(factoryBuilder);
secondSignatureWallet = buildSecondSignatureWallet(factoryBuilder);
multiSignatureWallet = buildMultiSignatureWallet();
multiSignatureRecipientWallet = buildMultiSignatureRecipientWallet();
recipientWallet = buildRecipientWallet(factoryBuilder);

walletRepository.index(senderWallet);
walletRepository.index(secondSignatureWallet);
walletRepository.index(multiSignatureWallet);
walletRepository.index(multiSignatureRecipientWallet);
walletRepository.index(recipientWallet);
});

Expand Down Expand Up @@ -116,6 +122,7 @@ describe("TransferTransaction", () => {

afterEach(async () => {
Managers.configManager.set("network.pubKeyHash", pubKeyHash);

});

describe("bootstrap", () => {
Expand All @@ -142,12 +149,23 @@ describe("TransferTransaction", () => {
).toResolve();
});

it("should not throw - multi sign", async () => {
it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureTransferTransaction, multiSignatureWallet),
).toResolve();
});


it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
await expect(
handler.throwIfCannotBeApplied(multiSignatureTransferTransaction, multiSignatureWallet),
).rejects.toThrow(
DisabledMultiSignatureSending,
);
});

it("should throw", async () => {
transferTransaction.data.senderPublicKey = "a".repeat(66);
await expect(handler.throwIfCannotBeApplied(transferTransaction, senderWallet)).rejects.toThrow(
Expand Down Expand Up @@ -185,6 +203,34 @@ describe("TransferTransaction", () => {
);
});

it("should pass if recipient is multi signature wallet and multiSignatureReceivingEnabled=true", async () => {
Managers.configManager.getMilestone().multiSignatureReceivingEnabled = true;

transferTransaction = BuilderFactory.transfer()
.recipientId(multiSignatureRecipientWallet.getAddress())
.amount("10000000")
.sign(passphrases[0])
.nonce("1")
.build();

await expect(handler.throwIfCannotBeApplied(transferTransaction, senderWallet)).toResolve();
});

it("should throw if recipient is multi signature wallet and multiSignatureReceivingEnabled=false", async () => {
Managers.configManager.getMilestone().multiSignatureReceivingEnabled = false;

transferTransaction = BuilderFactory.transfer()
.recipientId(multiSignatureRecipientWallet.getAddress())
.amount("10000000")
.sign(passphrases[0])
.nonce("1")
.build();

await expect(handler.throwIfCannotBeApplied(transferTransaction, senderWallet)).rejects.toThrow(
DisabledMultiSignatureReceiving,
);
});

it("should not throw if recipient is cold wallet", async () => {
const coldWallet: Wallets.Wallet = factoryBuilder
.get("Wallet")
Expand Down
11 changes: 10 additions & 1 deletion __tests__/unit/core-transactions/handlers/two/vote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
InsufficientBalanceError,
NoVoteError,
UnvoteMismatchError,
DisabledMultiSignatureSending,
VotedForNonDelegateError,
} from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
Expand Down Expand Up @@ -297,12 +298,20 @@ describe("VoteTransaction", () => {
).toResolve();
});

it("should not throw - multi sign vote", async () => {
it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureVoteTransaction, multiSignatureWallet),
).toResolve();
});

it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
await expect(
handler.throwIfCannotBeApplied(multiSignatureVoteTransaction, multiSignatureWallet),
).rejects.toThrow(DisabledMultiSignatureSending);
});

it("should not throw if the unvote is valid and the wallet has voted", async () => {
senderWallet.setAttribute("vote", delegateWallet1.getPublicKey());
await expect(handler.throwIfCannotBeApplied(unvoteTransaction, senderWallet)).toResolve();
Expand Down
2 changes: 2 additions & 0 deletions packages/core-test-framework/src/app/generators/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ export class CryptoGenerator extends Generator {
blockBurnAddress: true,
blsPublicKeyRegistrationEnabled: true,
multiSignatureRegistrationEnabled: true,
multiSignatureSendingEnabled: true,
multiSignatureReceivingEnabled: true,
},
{
height: rewardHeight,
Expand Down
13 changes: 13 additions & 0 deletions packages/core-transactions/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ export class UnsupportedMultiSignatureTransactionError extends TransactionError
}
}

export class DisabledMultiSignatureSending extends TransactionError {
public constructor() {
super(`Failed to apply transaction, because sending from multi signature wallets is disabled.`);
}
}

export class DisabledMultiSignatureReceiving extends TransactionError {
public constructor() {
super(`Failed to apply transaction, because receiving on multi signature wallets is disabled.`);
}
}


export class InvalidSecondSignatureError extends TransactionError {
public constructor() {
super(`Failed to apply transaction, because the second signature could not be verified.`);
Expand Down
13 changes: 13 additions & 0 deletions packages/core-transactions/src/handlers/one/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { Interfaces, Managers, Transactions } from "@arkecosystem/crypto";

import { isRecipientOnActiveNetwork } from "../../utils";
import { TransactionHandler, TransactionHandlerConstructor } from "../transaction";
import {
DisabledMultiSignatureReceiving,
} from "../../errors";

// todo: revisit the implementation, container usage and arguments after core-database rework
// todo: replace unnecessary function arguments with dependency injection to avoid passing around references
Expand Down Expand Up @@ -30,6 +33,16 @@ export class TransferTransactionHandler extends TransactionHandler {
transaction: Interfaces.ITransaction,
sender: Contracts.State.Wallet,
): Promise<void> {
Utils.assert.defined<string>(transaction.data.recipientId);
const recipient: Contracts.State.Wallet = this.walletRepository.findByAddress(transaction.data.recipientId);

if (recipient.hasMultiSignature()) {
const milestone = Managers.configManager.getMilestone();
if (milestone.multiSignatureReceivingEnabled !== true) {
throw new DisabledMultiSignatureReceiving();
}
}

return super.throwIfCannotBeApplied(transaction, sender);
}

Expand Down
8 changes: 8 additions & 0 deletions packages/core-transactions/src/handlers/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import assert from "assert";

import {
ColdWalletError,
DisabledMultiSignatureSending,
InsufficientBalanceError,
InvalidMultiSignaturesError,
InvalidSecondSignatureError,
Expand Down Expand Up @@ -86,6 +87,13 @@ export abstract class TransactionHandler {

const senderWallet: Contracts.State.Wallet = this.walletRepository.findByAddress(sender.getAddress());

if (senderWallet.hasMultiSignature()) {
const milestone = Managers.configManager.getMilestone();
if (milestone.multiSignatureSendingEnabled !== true) {
throw new DisabledMultiSignatureSending();
}
}

AppUtils.assert.defined<string>(sender.getPublicKey());

if (!this.walletRepository.hasByPublicKey(sender.getPublicKey()!) && senderWallet.getBalance().isZero()) {
Expand Down
Loading
Loading