nem-voting
Version:
546 lines • 22.1 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const nem_library_1 = require("nem-library");
const utils_1 = require("./utils");
const constants_1 = require("./constants");
const rxjs_1 = require("rxjs");
/**
* VOTING FUNCTIONS
*/
/**
* getWhitelistResults(poll) returns the result object for the poll
*
* @param {BroadcastedPoll} poll - broadcasted poll
*
* @return {promise} - A promise that returns the result object of the poll
*/
const getWhitelistResultsPromise = (poll) => __awaiter(this, void 0, void 0, function* () {
if (poll.data.formData.type !== constants_1.PollConstants.WHITELIST_POLL || !poll.data.whitelist) {
throw new Error("Not a whitelist poll");
}
const whitelist = poll.data.whitelist.map((address) => address.plain());
let end = (poll.data.formData.doe < Date.now()) ? (poll.data.formData.doe) : (null);
let blockPromise;
if (poll.endBlock !== undefined) {
blockPromise = Promise.resolve(poll.endBlock);
}
else if (end !== null) {
blockPromise = utils_1.getHeightByTimestamp(end).first().toPromise();
}
else {
blockPromise = Promise.resolve(-1);
}
const endBlock = yield blockPromise;
poll.setEndBlock(endBlock);
// get all Transactions that can potentially be votes
const orderedAddresses = poll.data.options.map((option) => poll.getOptionAddress(option));
const optionTransactionPromises = orderedAddresses.map((address) => {
if (address === null) {
throw new Error("Error while counting votes");
}
return utils_1.getAllTransactions(address).first().toPromise();
});
let optionTransactions = yield Promise.all(optionTransactionPromises);
// Filter only confirmed Transactions. If the poll has ended filter out all the votes that confirmed after the end.
if (end !== null) {
optionTransactions = optionTransactions.map((transactions) => {
return transactions.filter((transaction) => {
return (transaction.isConfirmed() && transaction.getTransactionInfo().height <= endBlock);
});
});
}
else {
end = Date.now();
optionTransactions = optionTransactions.map((transactions) => {
return transactions.filter((transaction) => transaction.isConfirmed());
});
}
// Only ransactions with 0 xem and 0 mosaics (Invalidates votes from exchanges and other cheating attempts)
optionTransactions = optionTransactions.map((transactions) => {
return transactions.map((transaction) => {
return utils_1.getTransferTransaction(transaction);
}).filter((transaction) => {
return (!transaction.containsMosaics()) && (transaction.xem().amount === 0);
});
});
// convert public keys to addresses and filter by WhiteList
let voteAddresses = optionTransactions.map((transactions) => {
return transactions.map((transaction) => {
return transaction.signer.address;
}).filter((address) => whitelist.includes(address.plain()));
});
// eliminate repetitions in array (return array is sorted)
const unique = (addresses) => {
return addresses.sort((a, b) => (a.plain().localeCompare(b.plain())))
.filter((item, pos, ary) => {
return !pos || item.plain() !== ary[pos - 1].plain();
});
};
voteAddresses = voteAddresses.map(unique);
// merge for two sorted arrays
const merge = (a, b) => {
const answer = new Array(a.length + b.length);
let i = 0;
let j = 0;
let k = 0;
while (i < a.length && j < b.length) {
if (a[i].plain() < b[j].plain()) {
answer[k] = a[i];
i++;
}
else {
answer[k] = b[j];
j++;
}
k++;
}
while (i < a.length) {
answer[k] = a[i];
i++;
k++;
}
while (j < b.length) {
answer[k] = b[j];
j++;
k++;
}
return answer;
};
// merge addresses from all options (they remain sorted)
let allAddresses = voteAddresses.reduce(merge, []);
// we don't need to do anything if there are no votes
if (allAddresses.length === 0) {
return {
totalVotes: 0,
options: poll.data.options.map((option) => {
return {
text: option,
votes: 0,
weighted: 0,
percentage: 0,
};
}),
};
}
// if not multiple invalidate multiple votes
const occurences = {};
if (poll.data.formData.multiple) {
allAddresses.map((address) => {
if (!occurences[address.plain()]) {
occurences[address.plain()] = 1;
}
else {
occurences[address.plain()]++;
}
});
}
else {
// Since we deleted repeated votes in the same option, we can know all repetitions now mean they voted in more than one option
const nullified = allAddresses.filter((item, pos, ary) => {
return pos && item === ary[pos - 1];
}).map((address) => address.plain());
// remove null votes
voteAddresses = voteAddresses.map((addresses) => {
return addresses.filter((address) => (!nullified.includes(address.plain())));
});
allAddresses = allAddresses.filter((address) => (!nullified.includes(address.plain())));
allAddresses.forEach((address) => {
occurences[address.plain()] = 1;
});
}
// Only valid votes now on voteAddresses
// calculate weights
const weights = allAddresses.map((address) => 1 / occurences[address.plain()]);
const addressWeights = {}; // maps addresses to their importance
allAddresses.forEach((address, i) => {
addressWeights[address.plain()] = weights[i];
});
// count number of votes for each option
const voteCounts = voteAddresses.map((addresses) => addresses.length);
// count votes weighted
const voteCountsWeighted = voteAddresses.map((addresses) => {
return addresses.reduce((acc, v) => {
return acc + addressWeights[v.plain()];
}, 0);
});
const totalVotes = allAddresses.length;
const optionResults = poll.data.options.map((option, i) => {
// const percentage = (totalVotes === 0) ? (0) : (voteCountsWeighted[i] * 100 / unique(allAddresses).length);
const percentage = (totalVotes === 0) ? (0) : (voteCounts[i] * 100 / allAddresses.length);
return {
text: poll.data.options[i],
votes: voteCounts[i],
// weighted: voteCountsWeighted[i],
weighted: voteCounts[i],
percentage: (percentage),
};
});
return {
totalVotes: (totalVotes),
options: optionResults,
};
});
exports.getWhitelistResultsPromise = getWhitelistResultsPromise;
const getWhitelistResults = (poll) => {
return rxjs_1.Observable.fromPromise(getWhitelistResultsPromise(poll));
};
exports.getWhitelistResults = getWhitelistResults;
/**
* getPOIResults(poll) returns the result object for the poll
*
* @param {BroadcastedPoll} poll - broadcasted poll
*
* @return {promise} - A promise that returns the result object of the poll
*/
const getPOIResultsPromise = (poll) => __awaiter(this, void 0, void 0, function* () {
try {
if (poll.data.formData.type !== constants_1.PollConstants.POI_POLL) {
throw new Error("Not a POI poll");
}
let end = (poll.data.formData.doe < Date.now()) ? (poll.data.formData.doe) : (null);
let blockPromise;
if (poll.endBlock !== undefined) {
blockPromise = Promise.resolve(poll.endBlock);
}
else if (end !== null) {
blockPromise = utils_1.getHeightByTimestamp(end).first().toPromise();
}
else {
blockPromise = Promise.resolve(-1);
}
const endBlock = yield blockPromise;
poll.setEndBlock(endBlock);
// get all Transactions that can potentially be votes
const orderedAddresses = poll.data.options.map((option) => poll.getOptionAddress(option));
const optionTransactionPromises = orderedAddresses.map((address) => {
if (address === null) {
throw new Error("Error while counting votes");
}
return utils_1.getAllTransactions(address).first().toPromise();
});
let optionTransactions = yield Promise.all(optionTransactionPromises);
// Filter only confirmed Transactions. If the poll has ended filter out all the votes that confirmed after the end.
if (end !== null) {
optionTransactions = optionTransactions.map((transactions) => {
return transactions.filter((transaction) => {
return (transaction.isConfirmed() && transaction.getTransactionInfo().height <= endBlock);
});
});
}
else {
end = -1;
optionTransactions = optionTransactions.map((transactions) => {
return transactions.filter((transaction) => transaction.isConfirmed());
});
}
// Only transactions with 0 xem and 0 mosaics (Invalidates votes from exchanges and other cheating attempts)
optionTransactions = optionTransactions.map((transactions) => {
return transactions.map((transaction) => {
return utils_1.getTransferTransaction(transaction);
}).filter((transaction) => {
return (!transaction.containsMosaics()) && (transaction.xem().amount === 0);
});
});
// convert public keys to addresses
let voteAddresses = optionTransactions.map((transactions) => {
return transactions.map((transaction) => transaction.signer.address);
});
// eliminate repetitions in array (return array is sorted)
const unique = (addresses) => {
return addresses.sort((a, b) => (a.plain().localeCompare(b.plain())))
.filter((item, pos, ary) => {
return (pos === 0) || item.plain() !== ary[pos - 1].plain();
});
};
voteAddresses = voteAddresses.map(unique);
// merge for two sorted arrays
const merge = (a, b) => {
const answer = new Array(a.length + b.length);
let i = 0;
let j = 0;
let k = 0;
while (i < a.length && j < b.length) {
if (a[i].plain() < b[j].plain()) {
answer[k] = a[i];
i++;
}
else {
answer[k] = b[j];
j++;
}
k++;
}
while (i < a.length) {
answer[k] = a[i];
i++;
k++;
}
while (j < b.length) {
answer[k] = b[j];
j++;
k++;
}
return answer;
};
// merge addresses from all options (they remain sorted)
let allAddresses = voteAddresses.reduce(merge, []);
// we don't need to do anything if there are no votes
if (allAddresses.length === 0) {
return {
totalVotes: 0,
options: poll.data.options.map((option) => {
return {
text: option,
votes: 0,
weighted: 0,
percentage: 0,
};
}),
};
}
// if not multiple invalidate multiple votes
const occurences = {};
if (poll.data.formData.multiple) {
allAddresses.map((address) => {
if (!occurences[address.plain()]) {
occurences[address.plain()] = 1;
}
else {
occurences[address.plain()]++;
}
});
}
else {
// Since we deleted repeated votes in the same option, we can know all repetitions now mean they voted in more than one option
const nullified = allAddresses.filter((item, pos, ary) => {
return pos && item.plain() === ary[pos - 1].plain();
}).map((address) => address.plain());
// remove null votes
voteAddresses = voteAddresses.map((addresses) => {
return addresses.filter((address) => (!nullified.includes(address.plain())));
});
allAddresses = allAddresses.filter((address) => (!nullified.includes(address.plain())));
allAddresses.map((address) => {
occurences[address.plain()] = 1;
});
}
// We only want to query for importance once for every account
const uniqueAllAddresses = unique(allAddresses);
// Only valid votes now on voteAddresses and allAddresses
// Get Importances
const importances = yield utils_1.getImportances(uniqueAllAddresses, endBlock).first().toPromise();
const weightedImportances = importances.map((importance, i) => {
return importance /= occurences[uniqueAllAddresses[i].plain()];
});
const totalImportance = importances.reduce((a, b) => {
return a + b;
}, 0);
const addressImportances = {};
uniqueAllAddresses.forEach((address, i) => {
addressImportances[address.plain()] = weightedImportances[i];
});
// count number of votes for each option
const voteCounts = voteAddresses.map((addresses) => addresses.length);
// count votes weighted by importance
const voteCountsWeighted = voteAddresses.map((addresses) => {
return addresses.reduce((acc, v) => {
return acc + addressImportances[v.plain()];
}, 0);
});
const totalVotes = allAddresses.length;
const optionResults = poll.data.options.map((option, i) => {
const percentage = (totalImportance === 0) ? (0) : (voteCountsWeighted[i] * 100 / totalImportance);
return {
text: poll.data.options[i],
votes: voteCounts[i],
weighted: voteCountsWeighted[i],
percentage: (percentage),
};
});
return {
totalVotes: (totalVotes),
options: optionResults,
};
}
catch (err) {
throw err;
}
});
exports.getPOIResultsPromise = getPOIResultsPromise;
const getPOIResults = (poll) => {
return rxjs_1.Observable.fromPromise(getPOIResultsPromise(poll));
};
exports.getPOIResults = getPOIResults;
const toCsv = (o) => {
const keys = Object.keys(o);
const params = Object.keys(o[keys[0]]);
let resultString = params[0];
params.forEach((param, i) => {
if (i !== 0) {
resultString += "," + param;
}
});
resultString += "\n";
keys.forEach((key) => {
resultString += o[key][params[0]];
params.forEach((param, i) => {
if (i !== 0) {
resultString += "," + o[key][param];
}
});
resultString += "\n";
});
return resultString;
};
const toArray = (o) => {
const keys = Object.keys(o);
return keys.map((k) => o[k]);
};
const getPOIVotes = (poll) => __awaiter(this, void 0, void 0, function* () {
try {
if (poll.data.formData.type !== constants_1.PollConstants.POI_POLL) {
throw new Error("Not a POI poll");
}
const end = (poll.data.formData.doe < Date.now()) ? (poll.data.formData.doe) : (null);
let blockPromise;
if (poll.endBlock !== undefined) {
blockPromise = Promise.resolve(poll.endBlock);
}
else if (end !== null) {
blockPromise = utils_1.getHeightByTimestamp(end).first().toPromise();
}
else {
blockPromise = Promise.resolve(-1);
}
const endBlock = yield blockPromise;
poll.setEndBlock(endBlock);
// get all Transactions that can potentially be votes
const orderedAddresses = poll.data.options.map((option) => poll.getOptionAddress(option));
const optionTransactionPromises = orderedAddresses.map((address) => {
if (address === null) {
throw new Error("Error while counting votes");
}
return utils_1.getAllTransactions(address).first().toPromise();
});
let optionTransactions = yield Promise.all(optionTransactionPromises);
// filter unconfirmed
optionTransactions = optionTransactions.map((transactions) => {
return transactions.filter((transaction) => transaction.isConfirmed());
});
const votesObj = {};
// get individual information
optionTransactions.forEach((transactions, i) => {
transactions.forEach((trans) => {
const transaction = utils_1.getTransferTransaction(trans);
const address = transaction.signer.address.plain();
const block = transaction.getTransactionInfo().height;
const multisig = (trans.type === nem_library_1.TransactionTypes.MULTISIG);
let validity = "Valid";
if (endBlock > 0 && block > endBlock) {
validity = "Too Late";
}
if (transaction.containsMosaics() || !(transaction.xem().amount === 0)) {
validity = "Not a 0xem transaction";
}
const opt = poll.data.options[i];
votesObj[transaction.signer.address.plain()] = {
address,
block,
validity,
multisig,
option: opt,
importance: 0,
};
});
});
optionTransactions = optionTransactions.map((transactions) => {
return transactions.map((transaction) => {
return utils_1.getTransferTransaction(transaction);
});
});
let voteAddresses = optionTransactions.map((transactions) => {
return transactions.map((transaction) => transaction.signer.address);
});
// eliminate repetitions in array (return array is sorted)
const unique = (addresses) => {
return addresses.sort((a, b) => (a.plain().localeCompare(b.plain())))
.filter((item, pos, ary) => {
return !pos || item.plain() !== ary[pos - 1].plain();
});
};
voteAddresses = voteAddresses.map(unique);
// merge for two sorted arrays
const merge = (a, b) => {
const answer = new Array(a.length + b.length);
let i = 0;
let j = 0;
let k = 0;
while (i < a.length && j < b.length) {
if (a[i].plain() < b[j].plain()) {
answer[k] = a[i];
i++;
}
else {
answer[k] = b[j];
j++;
}
k++;
}
while (i < a.length) {
answer[k] = a[i];
i++;
k++;
}
while (j < b.length) {
answer[k] = b[j];
j++;
k++;
}
return answer;
};
// merge addresses from all options (they remain sorted)
const allAddresses = voteAddresses.reduce(merge, []);
// we don't need to do anything if there are no votes
if (allAddresses.length === 0) {
return {};
}
// Since we deleted repeated votes in the same option, we can know all repetitions now mean they voted in more than one option
const nullified = allAddresses.filter((item, pos, ary) => {
return pos && item.plain() === ary[pos - 1].plain();
}).map((address) => address.plain());
// mark null votes
nullified.forEach((address) => {
votesObj[address].validity = "Multiple Vote";
});
// We only want to query for importance once for every account
const uniqueAllAddresses = unique(allAddresses);
// Get Importances
const importances = yield utils_1.getImportances(uniqueAllAddresses, endBlock).first().toPromise();
uniqueAllAddresses.forEach((address, i) => {
votesObj[address.plain()].importance = importances[i];
});
return votesObj;
}
catch (err) {
throw err;
}
});
const getPOIResultsCsv = (poll) => {
return rxjs_1.Observable.fromPromise(getPOIVotes(poll))
.map((votes) => toCsv(votes));
};
exports.getPOIResultsCsv = getPOIResultsCsv;
const getPOIResultsArray = (poll) => {
return rxjs_1.Observable.fromPromise(getPOIVotes(poll))
.map((votes) => toArray(votes));
};
exports.getPOIResultsArray = getPOIResultsArray;
//# sourceMappingURL=counting.js.map