Skip to content

Commit 5776201

Browse files
committed
1. Addes service for Aggregated Transaction - isComplete 2. Fixed couple of bugs in TransactionMappings
1 parent 8f36562 commit 5776201

File tree

4 files changed

+374
-4
lines changed

4 files changed

+374
-4
lines changed

src/infrastructure/transaction/CreateTransactionFromPayload.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ const CreateTransaction = (type: number, transactionData: string, networkType: N
377377
networkType,
378378
consignatureArray ? consignatureArray.map((cosignature) => new AggregateTransactionCosignature(
379379
cosignature.substring(0, 64),
380-
PublicAccount.createFromPublicKey(cosignature.substring(64, 192), networkType),
380+
PublicAccount.createFromPublicKey(cosignature.substring(0, 64), networkType),
381381
)) : [],
382382
);
383383
case TransactionType.AGGREGATE_BONDED:
@@ -401,7 +401,7 @@ const CreateTransaction = (type: number, transactionData: string, networkType: N
401401
networkType,
402402
bondedConsignatureArray ? bondedConsignatureArray.map((cosignature) => new AggregateTransactionCosignature(
403403
cosignature.substring(0, 64),
404-
PublicAccount.createFromPublicKey(cosignature.substring(64, 192), networkType),
404+
PublicAccount.createFromPublicKey(cosignature.substring(0, 64), networkType),
405405
)) : [],
406406
);
407407
default:
@@ -457,9 +457,9 @@ const parseInnerTransactionFromBinary = (innerTransactionBinary: string): string
457457

458458
while (innerBinary.length) {
459459
const payloadSize = parseInt(convert.uint8ToHex(convert.hexToUint8(innerTransactionBinary.substring(0, 8)).reverse()), 16) * 2;
460-
const innerTransaction = innerTransactionBinary.substring(8, 8 + payloadSize);
460+
const innerTransaction = innerTransactionBinary.substring(8, payloadSize);
461461
embeddedTransaction.push(innerTransaction);
462-
innerBinary = innerTransactionBinary.substring(8 + payloadSize);
462+
innerBinary = innerBinary.substring(payloadSize);
463463
}
464464
return embeddedTransaction;
465465
};
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2019 NEM
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {from as observableFrom , Observable, of as observableOf} from 'rxjs';
18+
import { map, mergeMap} from 'rxjs/operators';
19+
import { TransactionMapping } from '../core/utils/TransactionMapping';
20+
import { AccountHttp } from '../infrastructure/AccountHttp';
21+
import { MultisigAccountGraphInfo } from '../model/account/MultisigAccountGraphInfo';
22+
import { AggregateTransaction } from '../model/transaction/AggregateTransaction';
23+
import { SignedTransaction } from '../model/transaction/SignedTransaction';
24+
25+
/**
26+
* Aggregated Transaction service
27+
*/
28+
export class AggregatedTransactionService {
29+
30+
/**
31+
* Constructor
32+
* @param accountHttp
33+
*/
34+
constructor(private readonly accountHttp: AccountHttp) {
35+
}
36+
37+
/**
38+
* Check if an aggregate complete transaction has all cosignatories attached
39+
* @param signedTransaction - The signed aggregate transaction (complete) to be verified
40+
* @returns {Observable<boolean>}
41+
*/
42+
public isComplete(signedTransaction: SignedTransaction): Observable<boolean> {
43+
const aggregateTransaction = TransactionMapping.createFromPayload(signedTransaction.payload) as AggregateTransaction;
44+
/**
45+
* Include both initiator & cosigners
46+
*/
47+
const signers = (aggregateTransaction.cosignatures.map((cosigner) => cosigner.signer.publicKey));
48+
if (signedTransaction.signer) {
49+
signers.push(signedTransaction.signer);
50+
}
51+
52+
return observableFrom(aggregateTransaction.innerTransactions).pipe(
53+
mergeMap((innerTransaction) => this.accountHttp.getMultisigAccountInfo(innerTransaction.signer.address)
54+
.pipe(
55+
/**
56+
* For multisig account, we need to get the graph info in case it has multiple levels
57+
*/
58+
mergeMap((_) => _.minApproval !== 0 && _.minRemoval !== 0 ?
59+
this.accountHttp.getMultisigAccountGraphInfo(_.account.address)
60+
.pipe(
61+
map((graphInfo) => this.validateCosignatories(graphInfo, signers)),
62+
) : observableOf(true),
63+
),
64+
),
65+
),
66+
);
67+
}
68+
69+
/**
70+
* Validate cosignatories against multisig Account(s)
71+
* @param graphInfo - multisig account graph info
72+
* @param cosignatories - array of cosignatories extracted from aggregated transaction
73+
* @returns {boolean}
74+
*/
75+
private validateCosignatories(graphInfo: MultisigAccountGraphInfo, cosignatories: string[]): boolean {
76+
/**
77+
* Validate cosignatories from bottom level to top
78+
*/
79+
const sortedKeys = Array.from(graphInfo.multisigAccounts.keys()).sort((a, b) => b - a);
80+
const cosignatoriesReceived = cosignatories;
81+
let validationResult = true;
82+
83+
sortedKeys.forEach((key) => {
84+
const multisigInfo = graphInfo.multisigAccounts.get(key);
85+
if (multisigInfo && validationResult) {
86+
multisigInfo.forEach((multisig) => {
87+
if (multisig.minApproval >= 1) {
88+
const matchedCosignatories = this.compareArrays(cosignatoriesReceived,
89+
multisig.cosignatories.map((cosig) => cosig.publicKey));
90+
91+
/**
92+
* if minimal signature requirement met at current level, push the multisig account
93+
* into the received signatories array for next level validation.
94+
* Otherwise return validation failed.
95+
*/
96+
if (matchedCosignatories.length >= multisig.minApproval) {
97+
if (cosignatoriesReceived.indexOf(multisig.account.publicKey) === -1) {
98+
cosignatoriesReceived.push(multisig.account.publicKey);
99+
}
100+
} else {
101+
validationResult = false;
102+
}
103+
}
104+
});
105+
}
106+
});
107+
108+
return validationResult;
109+
}
110+
111+
/**
112+
* Compare two string arrays
113+
* @param array1 - base array
114+
* @param array2 - array to be matched
115+
* @returns {string[]} - array of matched elements
116+
*/
117+
private compareArrays(array1: string[], array2: string[]): string[] {
118+
const results: string[] = [];
119+
array1.forEach((a1) => array2.forEach((a2) => {
120+
if (a1 === a2) {
121+
results.push(a1);
122+
}
123+
}));
124+
125+
return results;
126+
}
127+
}

src/service/service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616

1717
export * from './NamespaceService';
1818
export * from './MosaicService';
19+
export * from './AggregatedTransactionService';

0 commit comments

Comments
 (0)