Skip to content
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
2 changes: 1 addition & 1 deletion e2e/e2e-preset.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
nemesis:
mosaics:
- accounts: 10
symbolRestImage: symbolplatform/symbol-rest:2.1.1-alpha
symbolRestImage: symbolplatform/symbol-rest:2.1.1-alpha-202011061935
10 changes: 6 additions & 4 deletions e2e/infrastructure/IntegrationTestHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ export class IntegrationTestHelper {
public config: StartParams;
public startEachTime = true;
public epochAdjustment: number;
public bootstrapAddresses: Addresses;

private async startBootstrapServer(): Promise<{ accounts: string[]; apiUrl: string }> {
private async startBootstrapServer(): Promise<{ accounts: string[]; apiUrl: string; addresses: Addresses }> {
this.config = {
report: false,
preset: Preset.bootstrap,
Expand All @@ -65,20 +66,20 @@ export class IntegrationTestHelper {
const configResult = await this.service.start({ ...this.config, detached: true });
return this.toAccounts(configResult.addresses);
}
private async loadBootstrap(): Promise<{ accounts: string[]; apiUrl: string }> {
private async loadBootstrap(): Promise<{ accounts: string[]; apiUrl: string; addresses: Addresses }> {
// const target = '../catapult-rest/rest/target';
const target = 'target/bootstrap-test';
console.log('Loading bootstrap server');
const addresses = BootstrapUtils.loadExistingAddresses(target);
return this.toAccounts(addresses);
}

private toAccounts(addresses: Addresses): { accounts: string[]; apiUrl: string } {
private toAccounts(addresses: Addresses): { accounts: string[]; apiUrl: string; addresses: Addresses } {
const accounts = addresses?.mosaics?.[0].accounts.map((n) => n.privateKey);
if (!accounts) {
throw new Error('Nemesis accounts could not be loaded!');
}
return { accounts, apiUrl: 'http://localhost:3000' };
return { accounts, apiUrl: 'http://localhost:3000', addresses };
}

async close(): Promise<void> {
Expand All @@ -94,6 +95,7 @@ export class IntegrationTestHelper {
// await this.service.stop(this.config);
const config = await this.loadBootstrap();
const accounts = config.accounts;
this.bootstrapAddresses = config.addresses;
this.apiUrl = config.apiUrl;
this.repositoryFactory = new RepositoryFactoryHttp(this.apiUrl);
this.transactionService = new TransactionService(
Expand Down
88 changes: 62 additions & 26 deletions e2e/infrastructure/PersistentHarvesting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,38 @@
* limitations under the License.
*/

import { Account } from '../../src/model/account/Account';
import { NetworkType } from '../../src/model/network/NetworkType';
import { AccountKeyLinkTransaction } from '../../src/model/transaction/AccountKeyLinkTransaction';
import { Deadline } from '../../src/model/transaction/Deadline';
import { LinkAction } from '../../src/model/transaction/LinkAction';
import { NodeKeyLinkTransaction } from '../../src/model/transaction/NodeKeyLinkTransaction';
import { PersistentDelegationRequestTransaction } from '../../src/model/transaction/PersistentDelegationRequestTransaction';
import { VrfKeyLinkTransaction } from '../../src/model/transaction/VrfKeyLinkTransaction';
import { Account } from '../../src/model/account';
import { NetworkType } from '../../src/model/network';
import {
AccountKeyLinkTransaction,
Deadline,
LinkAction,
NodeKeyLinkTransaction,
PersistentDelegationRequestTransaction,
VrfKeyLinkTransaction,
} from '../../src/model/transaction';
import { IntegrationTestHelper } from './IntegrationTestHelper';

describe('PersistentHarvesting', () => {
const helper = new IntegrationTestHelper();
let account: Account;
let generationHash: string;
let networkType: NetworkType;
let vrfAccount: Account;
let remoteAccount: Account;

const vrfKeyPair = Account.createFromPrivateKey(
'82798EA9A2D2D202AFCCC82C40A287780BCA3C7F7A2FD5B754832804C6BE1BAA',
NetworkType.PRIVATE_TEST,
);
let nodePublicKey: string;

before(() => {
return helper.start({ openListener: true }).then(() => {
remoteAccount = Account.generateNewAccount(helper.networkType);
console.log(remoteAccount.privateKey, remoteAccount.publicAccount);
account = helper.harvestingAccount;
generationHash = helper.generationHash;
networkType = helper.networkType;
remoteAccount = Account.createFromPrivateKey('CC798EA9A2D2D202AFCCC82C40A287780BCA3C7F7A2FD5B754832804C6BE1BAA', networkType);
vrfAccount = Account.createFromPrivateKey('AA798EA9A2D2D202AFCCC82C40A287780BCA3C7F7A2FD5B754832804C6BE1BAA', networkType);
account = helper.account;
generationHash = helper.generationHash;
nodePublicKey = helper.bootstrapAddresses.nodes![0].signing!.publicKey;
console.log('Remote: ', remoteAccount.publicAccount);
console.log('VRF: ', vrfAccount.publicAccount);
console.log('Node Public Key: ', nodePublicKey);
});
});

Expand All @@ -57,7 +60,16 @@ describe('PersistentHarvesting', () => {
*/

describe('AccountKeyLinkTransaction', () => {
it('standalone', () => {
it('standalone', async () => {
const accountInfo = await helper.repositoryFactory.createAccountRepository().getAccountInfo(account.address).toPromise();
const publicKey = accountInfo.supplementalPublicKeys?.linked?.publicKey;
if (publicKey) {
if (publicKey == remoteAccount.publicKey) {
return;
}
throw new Error(`Account ${accountInfo.address.plain()} already linked to another Remote public key ${publicKey}`);
}

const accountLinkTransaction = AccountKeyLinkTransaction.create(
Deadline.create(helper.epochAdjustment),
remoteAccount.publicKey,
Expand All @@ -72,10 +84,20 @@ describe('PersistentHarvesting', () => {
});

describe('VrfKeyLinkTransaction', () => {
it('standalone', () => {
it('standalone', async () => {
const accountInfo = await helper.repositoryFactory.createAccountRepository().getAccountInfo(account.address).toPromise();

const publicKey = accountInfo.supplementalPublicKeys?.vrf?.publicKey;
if (publicKey) {
if (publicKey == vrfAccount.publicKey) {
return;
}
throw new Error(`Account ${accountInfo.address.plain()} already linked to another VRF public key ${publicKey}`);
}

const vrfKeyLinkTransaction = VrfKeyLinkTransaction.create(
Deadline.create(helper.epochAdjustment),
vrfKeyPair.publicKey,
vrfAccount.publicKey,
LinkAction.Link,
networkType,
helper.maxFee,
Expand All @@ -87,10 +109,22 @@ describe('PersistentHarvesting', () => {
});

describe('NodeKeyLinkTransaction', () => {
it('standalone', () => {
it('standalone', async () => {
const nodePublicKey = helper.bootstrapAddresses.nodes![0].signing!.publicKey;

const accountInfo = await helper.repositoryFactory.createAccountRepository().getAccountInfo(account.address).toPromise();

const publicKey = accountInfo.supplementalPublicKeys?.node?.publicKey;
if (publicKey) {
if (publicKey == nodePublicKey) {
return;
}
throw new Error(`Account ${accountInfo.address.plain()} already linked to another Node public key ${publicKey}`);
}

const nodeKeyLinkTransaction = NodeKeyLinkTransaction.create(
Deadline.create(helper.epochAdjustment),
'cfd84eca83508bbee954668e4aecca736caefa615367da76afe6985d695381db',
nodePublicKey,
LinkAction.Link,
networkType,
helper.maxFee,
Expand All @@ -107,15 +141,17 @@ describe('PersistentHarvesting', () => {
*/

describe('transactions', () => {
it('should create delegated harvesting transaction', () => {
it('should create delegated harvesting transaction', async () => {
const nodePublicKey = helper.bootstrapAddresses.nodes![0].signing!.publicKey;
const tx = PersistentDelegationRequestTransaction.createPersistentDelegationRequestTransaction(
Deadline.create(helper.epochAdjustment),
remoteAccount.privateKey,
vrfKeyPair.privateKey,
'cfd84eca83508bbee954668e4aecca736caefa615367da76afe6985d695381db',
NetworkType.PRIVATE_TEST,
vrfAccount.privateKey,
nodePublicKey,
networkType,
helper.maxFee,
);
console.log(tx.message.toDTO());

const signedTransaction = tx.signWith(account, generationHash);
return helper.announce(signedTransaction);
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"bootstrap-clean": "symbol-bootstrap clean -t target/bootstrap-test",
"bootstrap-start-detached": "symbol-bootstrap start -r -c ./e2e/e2e-preset.yml -t target/bootstrap-test --timeout 120000 -d --healthCheck",
"bootstrap-start": "symbol-bootstrap start -r -c ./e2e/e2e-preset.yml --timeout 120000 -t target/bootstrap-test",
"bootstrap-upgrade": "symbol-bootstrap compose -r -t target/bootstrap-test",
"bootstrap-stop": "symbol-bootstrap stop -t target/bootstrap-test",
"bootstrap-run": "symbol-bootstrap run -t target/bootstrap-test",
"bootstrap-compose": "symbol-bootstrap compose -t target/bootstrap-test",
Expand Down
6 changes: 5 additions & 1 deletion src/model/message/EncryptedMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { Crypto } from '../../core/crypto';
import { PublicAccount } from '../account/PublicAccount';
import { PublicAccount } from '../account';
import { Message } from './Message';
import { MessageType } from './MessageType';
import { PlainMessage } from './PlainMessage';
Expand Down Expand Up @@ -46,7 +46,11 @@ export class EncryptedMessage extends Message {
}

/**
* It creates a encrypted message from the payload hex wihtout the 01 prefix.
*
* The 01 prefix will be attached to the final payload.
*
* @internal
* @param payload
*/
public static createFromPayload(payload: string): EncryptedMessage {
Expand Down
18 changes: 11 additions & 7 deletions src/model/message/MessageFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,27 @@ export class MessageFactory {
}
/**
* It creates a message from the hex payload
* @param payload the payload as byte array
* @param payload the payload as hex
*/
public static createMessageFromHex(payload?: string): Message {
if (!payload || !payload.length) {
return new RawMessage('');
}
if (payload.length == 264 && payload.startsWith(MessageMarker.PersistentDelegationUnlock)) {
return new PersistentHarvestingDelegationMessage(payload);
const upperCasePayload = payload.toUpperCase();
if (
upperCasePayload.length == PersistentHarvestingDelegationMessage.HEX_PAYLOAD_SIZE &&
upperCasePayload.startsWith(MessageMarker.PersistentDelegationUnlock)
) {
return PersistentHarvestingDelegationMessage.createFromPayload(upperCasePayload);
}
const messageType = Convert.hexToUint8(payload)[0];
const messageType = Convert.hexToUint8(upperCasePayload)[0];
switch (messageType) {
case MessageType.PlainMessage:
return new PlainMessage(Message.decodeHex(payload.substring(2)));
return PlainMessage.createFromPayload(upperCasePayload.substring(2));
case MessageType.EncryptedMessage:
return new EncryptedMessage(Message.decodeHex(payload.substring(2)));
return EncryptedMessage.createFromPayload(upperCasePayload.substring(2));
}
return new RawMessage(payload);
return new RawMessage(upperCasePayload);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/model/message/MessageMarker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

export class MessageMarker {
/**
* 8-byte marker: E201735761802AFE for PersistentDelegationRequestTransaction message
* 8-byte marker: FE2A8061577301E2 for PersistentDelegationRequestTransaction message
*/
public static readonly PersistentDelegationUnlock = 'E201735761802AFE';
public static readonly PersistentDelegationUnlock = 'FE2A8061577301E2';
}
27 changes: 20 additions & 7 deletions src/model/message/PersistentHarvestingDelegationMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,34 @@ import { MessageMarker } from './MessageMarker';
import { MessageType } from './MessageType';

export class PersistentHarvestingDelegationMessage extends Message {
public static readonly HEX_PAYLOAD_SIZE = 264;

constructor(payload: string) {
super(MessageType.PersistentHarvestingDelegationMessage, payload);
super(MessageType.PersistentHarvestingDelegationMessage, payload.toUpperCase());
if (!Convert.isHexString(payload)) {
throw Error('Payload format is not valid hexadecimal string');
}
if (payload.length != PersistentHarvestingDelegationMessage.HEX_PAYLOAD_SIZE) {
throw Error(
`Invalid persistent harvesting delegate payload size! Expected ${PersistentHarvestingDelegationMessage.HEX_PAYLOAD_SIZE} but got ${payload.length}`,
);
}
if (payload.toUpperCase().indexOf(MessageMarker.PersistentDelegationUnlock) != 0) {
throw Error(
`Invalid persistent harvesting delegate payload! It does not start with ${MessageMarker.PersistentDelegationUnlock}`,
);
}
}

/**
* @param signingPrivateKey - Remote harvester signing private key linked to the main account
* @param remoteLinkedPrivateKey - Remote harvester signing private key linked to the main account
* @param vrfPrivateKey - VRF private key linked to the main account
* @param nodePublicKey - Node certificate public key
* @param {NetworkType} networkType - Catapult network type
* @return {PersistentHarvestingDelegationMessage}
*/
public static create(
signingPrivateKey: string,
remoteLinkedPrivateKey: string,
vrfPrivateKey: string,
nodePublicKey: string,
networkType: NetworkType,
Expand All @@ -47,17 +59,18 @@ export class PersistentHarvestingDelegationMessage extends Message {
const encrypted =
MessageMarker.PersistentDelegationUnlock +
ephemeralKeypair.publicKey +
Crypto.encode(ephemeralKeypair.privateKey, nodePublicKey, signingPrivateKey + vrfPrivateKey, true).toUpperCase();
Crypto.encode(ephemeralKeypair.privateKey, nodePublicKey, remoteLinkedPrivateKey + vrfPrivateKey, true).toUpperCase();
return new PersistentHarvestingDelegationMessage(encrypted);
}

/**
* Create PersistentHarvestingDelegationMessage from DTO payload
* Create PersistentHarvestingDelegationMessage from DTO payload with marker.
* @internal
* @param payload
*
*/
public static createFromPayload(payload: string): PersistentHarvestingDelegationMessage {
const msgTypeHex = MessageType.PersistentHarvestingDelegationMessage.toString(16).toUpperCase();
return new PersistentHarvestingDelegationMessage(msgTypeHex + payload.toUpperCase());
return new PersistentHarvestingDelegationMessage(payload);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/model/message/PlainMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export class PlainMessage extends Message {
}

/**
*
* It creates the Plain message from a payload hex without the 00 prefix.
*
* The 00 prefix will be attached to the final payload.
*
* @internal
*/
public static createFromPayload(payload: string): PlainMessage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class PersistentDelegationRequestTransaction extends TransferTransaction
* Create a PersistentDelegationRequestTransaction with special message payload
* for presistent harvesting delegation unlocking
* @param deadline - The deadline to include the transaction.
* @param signingPrivateKey - Remote harvester signing private key linked to the main account
* @param remoteLinkedPrivateKey - Remote harvester signing private key linked to the main account
* @param vrfPrivateKey - VRF private key linked to the main account
* @param nodePublicKey - Node certificate public key
* @param networkType - The network type.
Expand All @@ -38,15 +38,15 @@ export class PersistentDelegationRequestTransaction extends TransferTransaction
*/
public static createPersistentDelegationRequestTransaction(
deadline: Deadline,
signingPrivateKey: string,
remoteLinkedPrivateKey: string,
vrfPrivateKey: string,
nodePublicKey: string,
networkType: NetworkType,
maxFee: UInt64 = new UInt64([0, 0]),
signature?: string,
signer?: PublicAccount,
): PersistentDelegationRequestTransaction {
const message = PersistentHarvestingDelegationMessage.create(signingPrivateKey, vrfPrivateKey, nodePublicKey, networkType);
const message = PersistentHarvestingDelegationMessage.create(remoteLinkedPrivateKey, vrfPrivateKey, nodePublicKey, networkType);
return super.create(
deadline,
Address.createFromPublicKey(nodePublicKey, networkType),
Expand Down
2 changes: 1 addition & 1 deletion test/core/utils/TransactionMapping.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1631,7 +1631,7 @@ describe('TransactionMapping - createFromDTO (Transaction.toJSON() feed)', () =>

it('should create from payload with persistent delegate message', () => {
const transferTransaction = TransactionMapping.createFromPayload(
'2401000000000000B69B5CB2CE79B5E06117D246E84CB21095BAC0E78A4F01E2D4E3F068EF2E3370B907495FE6BC9A130D05A8BDB49E8AD398FBA40569C921907297AB0D5778C00D2D13E2FDC654936417899AB63D709B9A7A603BA1D20557AA06CE5B2C9242F035000000000198544140420F00000000009A19B31107000000981E13520236DBBD06F8C08710289BD9CF598A01C29E04668400000000000000E201735761802AFECBD67E2DBA5D69157CE32874EFDD680E41B1BFFD12B781F84E8AD883920BBB50EA38AFE078E99EE5EE4BDFDA77E8C101EBD3100C0D471673471A61B23E513FC7E21F7803316B906A688F14AA75002913A3B57DD13469BC27CF8C82FD5C4C76867011AEDC7C4870D8C5AF9C175F0DA5A8E2AD3A327D868BFBA34A5E3D',
'2401000000000000B69B5CB2CE79B5E06117D246E84CB21095BAC0E78A4F01E2D4E3F068EF2E3370B907495FE6BC9A130D05A8BDB49E8AD398FBA40569C921907297AB0D5778C00D2D13E2FDC654936417899AB63D709B9A7A603BA1D20557AA06CE5B2C9242F035000000000198544140420F00000000009A19B31107000000981E13520236DBBD06F8C08710289BD9CF598A01C29E04668400000000000000FE2A8061577301E2CBD67E2DBA5D69157CE32874EFDD680E41B1BFFD12B781F84E8AD883920BBB50EA38AFE078E99EE5EE4BDFDA77E8C101EBD3100C0D471673471A61B23E513FC7E21F7803316B906A688F14AA75002913A3B57DD13469BC27CF8C82FD5C4C76867011AEDC7C4870D8C5AF9C175F0DA5A8E2AD3A327D868BFBA34A5E3D',
) as TransferTransaction;

expect(transferTransaction.message.type).eq(MessageType.PersistentHarvestingDelegationMessage);
Expand Down
Loading