Skip to content

Commit

Permalink
feat: initial implementation of AIP18 (#2508)
Browse files Browse the repository at this point in the history
  • Loading branch information
spkjp authored and faustbrian committed May 1, 2019
1 parent b611819 commit a4b9737
Show file tree
Hide file tree
Showing 97 changed files with 2,115 additions and 1,068 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,6 @@ npm-shrinkwrap.json
transaction-pool.sqlite
transaction-pool.sqlite-shm
transaction-pool.sqlite-wal

# P2P
/peers.json
51 changes: 28 additions & 23 deletions __tests__/functional/transaction-forging/__support__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,34 @@ let app;
export async function setUp() {
process.env.CORE_SKIP_COLD_START = "true";

app = await setUpContainer({
include: [
"@arkecosystem/core-event-emitter",
"@arkecosystem/core-logger-pino",
"@arkecosystem/core-state",
"@arkecosystem/core-database-postgres",
"@arkecosystem/core-transaction-pool",
"@arkecosystem/core-p2p",
"@arkecosystem/core-blockchain",
"@arkecosystem/core-api",
"@arkecosystem/core-forger",
],
});
try {
app = await setUpContainer({
include: [
"@arkecosystem/core-event-emitter",
"@arkecosystem/core-logger-pino",
"@arkecosystem/core-state",
"@arkecosystem/core-database-postgres",
"@arkecosystem/core-transaction-pool",
"@arkecosystem/core-p2p",
"@arkecosystem/core-blockchain",
"@arkecosystem/core-api",
"@arkecosystem/core-forger",
],
});

const databaseService = app.resolvePlugin("database");
await databaseService.reset();
await databaseService.buildWallets();
await databaseService.saveRound(
secrets.map(secret => ({
round: 1,
publicKey: Identities.PublicKey.fromPassphrase(secret),
voteBalance: Utils.BigNumber.make("245098000000000"),
})),
);
const databaseService = app.resolvePlugin("database");
await databaseService.reset();
await databaseService.buildWallets();
await databaseService.saveRound(
secrets.map(secret => ({
round: 1,
publicKey: Identities.PublicKey.fromPassphrase(secret),
voteBalance: Utils.BigNumber.make("245098000000000"),
})),
);
} catch (error) {
console.error(error.stack);
}
}

export async function tearDown() {
Expand All @@ -56,6 +60,7 @@ export async function expectAcceptAndBroadcast(transactions, id): Promise<void>
console.log(body.errors);
}

expect(body.errors).toBeUndefined();
expect(body.data.accept).toContain(id);
expect(body.data.broadcast).toContain(id);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Identities } from "@arkecosystem/crypto";
import { TransactionFactory } from "../../helpers/transaction-factory";
import { secrets } from "../../utils/config/testnet/delegates.json";
import * as support from "./__support__";

const { passphrase, secondPassphrase } = support.passphrases;

beforeAll(support.setUp);
afterAll(support.tearDown);

describe("Transaction Forging - Multi Signature Registration", () => {
it("should broadcast, accept and forge it [Signed with 1 Passphrase]", async () => {
// Funds to register a multi signature wallet
const initialFunds = TransactionFactory.transfer(Identities.Address.fromPassphrase(passphrase), 50 * 1e8)
.withPassphrase(secrets[0])
.create();

await support.expectAcceptAndBroadcast(initialFunds, initialFunds[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(initialFunds[0].id);

// Register a multi signature wallet with defaults
const passphrases = [passphrase, secrets[1], secrets[2]];
const participants = [
Identities.PublicKey.fromPassphrase(passphrases[0]),
Identities.PublicKey.fromPassphrase(passphrases[1]),
Identities.PublicKey.fromPassphrase(passphrases[2]),
];

const multiSignature = TransactionFactory.multiSignature(participants, 3)
.withPassphrase(passphrase)
.withPassphraseList(passphrases)
.create();

await support.expectAcceptAndBroadcast(multiSignature, multiSignature[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(multiSignature[0].id);
});

it("should broadcast, accept and forge it [Signed with 2 Passphrases]", async () => {
const passphrase = secrets[2];
// Make a fresh wallet for the second signature tests
const initialFunds = TransactionFactory.transfer(Identities.Address.fromPassphrase(passphrase), 100 * 1e8)
.withPassphrase(secrets[0])
.create();

await support.expectAcceptAndBroadcast(initialFunds, initialFunds[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(initialFunds[0].id);

// Register a second passphrase
const secondSignature = TransactionFactory.secondSignature(secondPassphrase)
.withPassphrase(passphrase)
.create();

await support.expectAcceptAndBroadcast(secondSignature, secondSignature[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(secondSignature[0].id);

// Register a multi signature wallet with defaults
const passphrases = [passphrase, secrets[3], secrets[4]];
const participants = [
Identities.PublicKey.fromPassphrase(passphrases[0]),
Identities.PublicKey.fromPassphrase(passphrases[1]),
Identities.PublicKey.fromPassphrase(passphrases[2]),
];

const multiSignature = TransactionFactory.multiSignature(participants, 3)
.withPassphraseList(passphrases)
.withPassphrasePair({ passphrase, secondPassphrase })
.create();

await support.expectAcceptAndBroadcast(multiSignature, multiSignature[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(multiSignature[0].id);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ beforeAll(support.setUp);
afterAll(support.tearDown);

describe("Transaction Forging - Second Signature Registration", () => {
it("should broadcast, accept and forge it [Signed with 1 Passphase]", async () => {
it("should broadcast, accept and forge it [Signed with 1 Passphrase]", async () => {
const secondSignature = TransactionFactory.secondSignature(support.passphrases.secondPassphrase)
.withPassphrase(secrets[0])
.create();
Expand Down
50 changes: 50 additions & 0 deletions __tests__/functional/transaction-forging/transfer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,54 @@ describe("Transaction Forging - Transfer", () => {
await support.snoozeForBlock(1);
await support.expectTransactionForged(transfer[0].id);
});

it("should broadcast, accept and forge it [3-of-3 multisig]", async () => {
// Funds to register a multi signature wallet
const initialFunds = TransactionFactory.transfer(Identities.Address.fromPassphrase(secrets[3]), 50 * 1e8)
.withPassphrase(secrets[0])
.create();

await support.expectAcceptAndBroadcast(initialFunds, initialFunds[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(initialFunds[0].id);

// Register a multi signature wallet with defaults
const passphrases = [secrets[3], secrets[4], secrets[5]];
const participants = [
Identities.PublicKey.fromPassphrase(passphrases[0]),
Identities.PublicKey.fromPassphrase(passphrases[1]),
Identities.PublicKey.fromPassphrase(passphrases[2]),
];

const multiSignature = TransactionFactory.multiSignature(participants, 3)
.withPassphrase(secrets[3])
.withPassphraseList(passphrases)
.create();

await support.expectAcceptAndBroadcast(multiSignature, multiSignature[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(multiSignature[0].id);

// Send funds to multi signature wallet
const multiSigAddress = Identities.Address.fromMultiSignatureAsset(multiSignature[0].asset.multiSignature);
const multiSigPublicKey = Identities.PublicKey.fromMultiSignatureAsset(multiSignature[0].asset.multiSignature);

const multiSignatureFunds = TransactionFactory.transfer(multiSigAddress, 20 * 1e8)
.withPassphrase(secrets[0])
.create();

await support.expectAcceptAndBroadcast(multiSignatureFunds, multiSignatureFunds[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(multiSignatureFunds[0].id);

// Create outgoing multi signature wallet transfer
const multiSigTransfer = TransactionFactory.transfer(Identities.Address.fromPassphrase(passphrase), 10 * 1e8)
.withSenderPublicKey(multiSigPublicKey)
.withPassphraseList(passphrases)
.create();

await support.expectAcceptAndBroadcast(multiSigTransfer, multiSigTransfer[0].id);
await support.snoozeForBlock(1);
await support.expectTransactionForged(multiSigTransfer[0].id);
});
});
72 changes: 63 additions & 9 deletions __tests__/helpers/transaction-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,42 @@ export class TransactionFactory {
);
}

public static multiSignature(participants?: string[], min?: number): TransactionFactory {
let passphrases: string[];
if (!participants) {
passphrases = [secrets[0], secrets[1], secrets[2]];
}

participants = participants || [
Identities.PublicKey.fromPassphrase(secrets[0]),
Identities.PublicKey.fromPassphrase(secrets[1]),
Identities.PublicKey.fromPassphrase(secrets[2]),
];

const factory: TransactionFactory = new TransactionFactory(
Transactions.BuilderFactory.multiSignature().multiSignatureAsset({
publicKeys: participants,
min: min || participants.length,
}),
);

if (passphrases) {
factory.withPassphraseList(passphrases);
}

factory.builder.senderPublicKey(participants[0]);
return factory;
}

private builder: any;
private network: Types.NetworkName = "testnet";
private fee: Utils.BigNumber;
private passphrase: string = defaultPassphrase;
private secondPassphrase: string;
private passphraseList: string[];
private passphrasePairs: PassphrasePair[];
private version: number;
private senderPublicKey: string;

public constructor(builder) {
this.builder = builder;
Expand All @@ -78,6 +107,18 @@ export class TransactionFactory {
return this;
}

public withSenderPublicKey(sender: string): TransactionFactory {
this.senderPublicKey = sender;

return this;
}

public withVersion(version: number): TransactionFactory {
this.version = version;

return this;
}

public withPassphrase(passphrase: string): TransactionFactory {
this.passphrase = passphrase;

Expand Down Expand Up @@ -127,12 +168,6 @@ export class TransactionFactory {
);
}

if (this.passphraseList && this.passphraseList.length) {
return this.passphraseList.map(
(passphrase: string) => this.withPassphrase(passphrase).sign<T>(quantity, method)[0],
);
}

return this.sign<T>(quantity, method);
}

Expand Down Expand Up @@ -162,14 +197,33 @@ export class TransactionFactory {
}
}

if (this.version) {
this.builder.version(this.version);
}

if (this.fee) {
this.builder.fee(this.fee.toFixed());
}

this.builder.sign(this.passphrase);
if (this.senderPublicKey) {
this.builder.senderPublicKey(this.senderPublicKey);
}

let sign: boolean = true;
if (this.passphraseList && this.passphraseList.length) {
sign = this.builder.constructor.name === "MultiSignatureBuilder";

if (this.secondPassphrase) {
this.builder.secondSign(this.secondPassphrase);
for (let i = 0; i < this.passphraseList.length; i++) {
this.builder.multiSign(this.passphraseList[i], i);
}
}

if (sign) {
this.builder.sign(this.passphrase);

if (this.secondPassphrase) {
this.builder.secondSign(this.secondPassphrase);
}
}

transactions.push(this.builder[method]());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { httpie } from "@arkecosystem/core-utils";
import { Transactions } from "@arkecosystem/crypto";
import "jest-extended";
import nock from "nock";
import { MultiSignatureRegistrationCommand } from "../../../../../packages/core-tester-cli/src/commands/send/multi-signature-registration";
import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared";

beforeEach(() => {
// Just passthru. We'll test the Command class logic in its own test file more thoroughly
nock("http://localhost:4003")
.get("/api/v2/node/configuration")
.thrice()
.reply(200, { data: { constants: {} } });

nock("http://localhost:4000")
.get("/config")
.thrice()
.reply(200, { data: { network: { name: "testnet" } } });

jest.spyOn(httpie, "get");
jest.spyOn(httpie, "post");
});

afterEach(() => {
nock.cleanAll();
});

describe("Commands - Multi signature registration", () => {
it("should apply multi signature", async () => {
const opts = {
multiSignatureFee: 1,
number: 1,
participants:
"03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37," +
"02def27da9336e7fbf63131b8d7e5c9f45b296235db035f1f4242c507398f0f21d," +
"0290907d441d257334c4376126d6cbf37cd7993ca2d0cc58850b30b869d4bf4c3e",
};

const expectedTransactions = [];
captureTransactions(nock, expectedTransactions);

await MultiSignatureRegistrationCommand.run(toFlags(opts));

expect(httpie.post).toHaveBeenCalledTimes(2);
expectTransactions(expectedTransactions, {
fee: arkToSatoshi(20),
asset: {
multiSignature: {
min: 3,
publicKeys: expect.toContainValues(opts.participants.split(",")),
},
},
});

expect(Transactions.Verifier.verify(expectedTransactions[0])).toBeTrue();
});
});
Loading

0 comments on commit a4b9737

Please sign in to comment.