UNPKG

factom-vote

Version:

JS implementation of the Factom voting specification

92 lines (72 loc) 4.1 kB
const { getPublicAddress } = require('factom'), { validateCreateVoteData } = require('../validation/json-validation'), { generateEligibleVotersChain, generateVoteChain, generateVoteRegistrationEntry, generateAppendEligibleVotersEntry } = require('./create-vote-struct'), { getVoteIdentity, extractKey, getPublicIdentityKey, getSecretIdentityKey } = require('../factom-identity'); async function createVote(cli, identityResolvers, voteData, ecAddress, skipValidation) { if (!validateCreateVoteData(voteData)) { throw new Error('Create vote data validation error:\n' + JSON.stringify(validateCreateVoteData.errors)); } const { definition, registrationChainId, eligibleVoters, identity } = voteData; if (!await cli.chainExists(registrationChainId)) { throw new Error(`Registration chain ${registrationChainId} doesn't exist.`); } const initiator = await getVoteIdentity(identityResolvers, identity); const defCopy = JSON.parse(JSON.stringify(definition)); const chainsToCreate = []; if (!defCopy.vote.eligibleVotersChainId) { if (!skipValidation) { const height = await cli.getHeights().then(heights => heights.leaderHeight); if (defCopy.vote.phasesBlockHeights.commitStart <= height + 1) { throw new Error(`The height of the current block being built (${height + 1}) is higher or equal to commitStart (${defCopy.vote.phasesBlockHeights.commitStart}) making your new eligible voters list unusable.`); } } const eligibleVotersChain = await generateEligibleVotersChain(eligibleVoters || [], initiator); defCopy.vote.eligibleVotersChainId = eligibleVotersChain.idHex; chainsToCreate.push(eligibleVotersChain); } const voteChain = await generateVoteChain(defCopy, initiator); chainsToCreate.push(voteChain); const registrationEntry = generateVoteRegistrationEntry(registrationChainId, voteChain.id); const ecCost = registrationEntry.ecCost() + chainsToCreate.map(c => c.ecCost()).reduce((acc, cur) => acc + cur, 0); await validateFunds(cli, ecCost, ecAddress, 'Cannot create vote'); const [voteChainAdded, eligibleVotersChainAdded] = await cli.add(chainsToCreate.reverse(), ecAddress); const registration = await cli.add(registrationEntry, ecAddress); return { eligibleVoters: eligibleVotersChainAdded || { chainId: defCopy.vote.eligibleVotersChainId }, vote: voteChainAdded, registration }; } async function appendEligibleVoters(cli, privateKeyResolver, appendEligibleVotersData, ecAddress, skipValidation) { const { eligibleVoters, eligibleVotersChainId, initiator } = appendEligibleVotersData; if (!skipValidation && initiator.key) { const canAppend = await canAppendEligibleVoters(cli, eligibleVotersChainId, initiator.key); if (!canAppend) { throw new Error(`The initiator secret key is not authorized to add eligible voters to [${eligibleVotersChainId}].`); } } if (typeof initiator.sign !== 'function') { initiator.secretKey = extractKey(await getSecretIdentityKey(privateKeyResolver, initiator.key)); } const entry = await generateAppendEligibleVotersEntry(eligibleVoters, eligibleVotersChainId, initiator); await validateFunds(cli, entry.ecCost(), ecAddress, 'Cannot append eligible voters'); return cli.add(entry, ecAddress); } async function validateFunds(cli, ecCost, ecAddress, message) { const balance = await cli.getBalance(ecAddress); if (ecCost > balance) { throw new Error(`${message}. Not enough EC on ${getPublicAddress(ecAddress)}. Required: ${ecCost}EC. Available: ${balance}EC`); } } async function canAppendEligibleVoters(cli, eligibleVotersChainId, identityKey) { const firstEntry = await cli.getFirstEntry(eligibleVotersChainId); const publicKey = extractKey(getPublicIdentityKey(identityKey)); return publicKey.equals(firstEntry.extIds[3]); } module.exports = { createVote, appendEligibleVoters };