@neo-one/node-blockchain-esnext-esm
Version:
NEO•ONE NEO blockchain implementation.
185 lines (183 loc) • 10.2 kB
JavaScript
import { common } from '@neo-one/client-common-esnext-esm';
import { BinaryReader, StateTransaction, TransactionType, utils, Validator, ValidatorsCount, } from '@neo-one/node-core-esnext-esm';
import BN from 'bn.js';
import _ from 'lodash';
import { ValidatorCache } from './ValidatorCache';
const processOutput = async (blockchain, cache, output, negative) => {
let { value } = output;
if (negative) {
value = value.neg();
}
const [account] = await Promise.all([
cache.getAccount(output.address),
cache.updateAccountBalance(output.address, output.asset, value),
]);
if (common.uInt256Equal(output.asset, blockchain.settings.governingToken.hash) && account.votes.length > 0) {
await Promise.all([
Promise.all(account.votes.map(async (publicKey) => cache.updateValidatorVotes(publicKey, value))),
cache.updateValidatorsCountVotes(account.votes.length - 1, value),
]);
}
};
const processTransaction = async (blockchain, cache, transaction) => {
let allOutputs = await Promise.all(transaction.inputs.map(async (input) => {
const output = await blockchain.output.get(input);
return { output, negative: true };
}));
allOutputs = allOutputs.concat(transaction.outputs.map((output) => ({ output, negative: false })));
await Promise.all(allOutputs.map(async ({ output, negative }) => processOutput(blockchain, cache, output, negative)));
const accountHashes = [...new Set(allOutputs.map(({ output }) => common.uInt160ToHex(output.address)))].map((hash) => common.hexToUInt160(hash));
const touchedValidators = await Promise.all(accountHashes.map(async (hash) => {
const account = await cache.getAccount(hash);
return account.votes;
}));
const touchedValidatorsSet = [
...new Set(touchedValidators.reduce((acc, votes) => acc.concat(votes.map((vote) => common.ecPointToHex(vote))), [])),
].map((publicKey) => common.hexToECPoint(publicKey));
await Promise.all(touchedValidatorsSet.map(async (publicKey) => {
const validator = await cache.getValidator(publicKey);
if (!validator.registered && validator.votes.eq(utils.ZERO)) {
await cache.deleteValidator(publicKey);
}
}));
};
export const getDescriptorChanges = async ({ transactions, getAccount, governingTokenHash, }) => {
const accountChanges = {};
const validatorVotesChanges = {};
const validatorRegisteredChanges = {};
const validatorsCountChanges = [];
const allDescriptors = transactions.reduce((acc, transaction) => acc.concat(transaction.descriptors), []);
const accountDescriptors = allDescriptors.filter((descriptor) => descriptor.type === 0x40);
const groupedAccountDescriptors = Object.entries(_.groupBy(accountDescriptors, (descriptor) => common.uInt160ToHex(common.bufferToUInt160(descriptor.key))));
await Promise.all(groupedAccountDescriptors.map(async ([hash, descriptors]) => {
const account = await getAccount(common.hexToUInt160(hash));
const balance = account.getBalance(governingTokenHash);
for (const vote of account.votes) {
const voteHex = common.ecPointToHex(vote);
validatorVotesChanges[voteHex] = (validatorVotesChanges[voteHex] === undefined
? utils.ZERO
: validatorVotesChanges[voteHex]).sub(balance);
}
const descriptor = descriptors[descriptors.length - 1];
const reader = new BinaryReader(descriptor.value);
const votes = reader.readArray(() => reader.readECPoint());
if (votes.length !== account.votes.length) {
if (account.votes.length > 0) {
validatorsCountChanges[account.votes.length - 1] = (validatorsCountChanges[account.votes.length - 1] === undefined
? utils.ZERO
: validatorsCountChanges[account.votes.length - 1]).sub(balance);
}
if (votes.length > 0) {
validatorsCountChanges[votes.length - 1] = (validatorsCountChanges[votes.length - 1] ===
undefined
? utils.ZERO
: validatorsCountChanges[votes.length - 1]).add(balance);
}
}
accountChanges[hash] = votes;
for (const vote of votes) {
const voteHex = common.ecPointToHex(vote);
validatorVotesChanges[voteHex] = (validatorVotesChanges[voteHex] === undefined
? utils.ZERO
: validatorVotesChanges[voteHex]).add(balance);
}
}));
const validatorDescriptors = allDescriptors.filter((descriptor) => descriptor.type === 0x48);
for (const descriptor of validatorDescriptors) {
const publicKey = common.bufferToECPoint(descriptor.key);
validatorRegisteredChanges[common.ecPointToHex(publicKey)] = descriptor.value.some((byte) => byte !== 0);
}
const validatorChanges = {};
for (const [publicKey, votes] of Object.entries(validatorVotesChanges)) {
validatorChanges[publicKey] = { votes };
}
for (const [publicKey, registered] of Object.entries(validatorRegisteredChanges)) {
const current = validatorChanges[publicKey] === undefined ? {} : validatorChanges[publicKey];
validatorChanges[publicKey] = {
registered,
votes: current.votes,
};
}
return {
accountChanges,
validatorChanges,
validatorsCountChanges,
};
};
export const processStateTransaction = async ({ validatorChanges, validatorsCountChanges, tryGetValidatorsCount, addValidatorsCount, updateValidatorsCount, tryGetValidator, addValidator, deleteValidator, updateValidator, }) => {
const validatorsCount = await tryGetValidatorsCount();
const mutableValidatorsCountVotes = validatorsCount === undefined ? [] : [...validatorsCount.votes];
[...validatorsCountChanges.entries()].forEach(([index, value]) => {
mutableValidatorsCountVotes[index] = value;
});
await Promise.all([
Promise.all(Object.entries(validatorChanges).map(async ([publicKeyHex, { registered, votes }]) => {
const publicKey = common.hexToECPoint(publicKeyHex);
const validator = await tryGetValidator({ publicKey });
if (validator === undefined) {
await addValidator(new Validator({
publicKey,
registered,
votes,
}));
}
else if (((registered !== undefined && !registered) || (registered === undefined && !validator.registered)) &&
((votes !== undefined && votes.eq(utils.ZERO)) || (votes === undefined && validator.votes.eq(utils.ZERO)))) {
await deleteValidator({ publicKey: validator.publicKey });
}
else {
await updateValidator(validator, { votes, registered });
}
})),
validatorsCount === undefined
? addValidatorsCount(new ValidatorsCount({
votes: mutableValidatorsCountVotes,
}))
: updateValidatorsCount(validatorsCount, {
votes: mutableValidatorsCountVotes,
}),
]);
};
export const getValidators = async (blockchain, transactions) => {
const cache = new ValidatorCache(blockchain);
await Promise.all(transactions.map(async (transaction) => processTransaction(blockchain, cache, transaction)));
const { validatorChanges, validatorsCountChanges } = await getDescriptorChanges({
transactions: transactions.filter((transaction) => transaction.type === TransactionType.State && transaction instanceof StateTransaction),
getAccount: async (hash) => cache.getAccount(hash),
governingTokenHash: blockchain.settings.governingToken.hashHex,
});
await processStateTransaction({
validatorChanges,
validatorsCountChanges,
tryGetValidatorsCount: async () => cache.getValidatorsCount(),
addValidatorsCount: async (value) => cache.addValidatorsCount(value),
updateValidatorsCount: async (update) => {
await cache.updateValidatorsCount(update);
},
tryGetValidator: async (key) => cache.getValidator(key.publicKey),
addValidator: async (validator) => cache.addValidator(validator),
deleteValidator: async (key) => cache.deleteValidator(key.publicKey),
updateValidator: async (value, update) => cache.updateValidator(value.publicKey, update),
});
const [validatorsCount, validators] = await Promise.all([cache.getValidatorsCount(), cache.getAllValidators()]);
const numValidators = Math.max(utils.weightedAverage(utils
.weightedFilter(validatorsCount.votes
.map((votes, count) => ({ count, votes: votes === undefined ? utils.ZERO : votes }))
.filter(({ votes }) => votes.gt(utils.ZERO)), 0.25, 0.75, ({ count }) => new BN(count))
.map(([{ count }, weight]) => ({ value: count, weight }))), blockchain.settings.standbyValidators.length);
const standbyValidatorsSet = new Set(blockchain.settings.standbyValidators.map((publicKey) => common.ecPointToHex(publicKey)));
const validatorsPublicKeySet = new Set(_.take(validators
.filter((validator) => (validator.registered && validator.votes.gt(utils.ZERO)) ||
standbyValidatorsSet.has(common.ecPointToHex(validator.publicKey)))
.sort((aValidator, bValidator) => aValidator.votes.eq(bValidator.votes)
? common.ecPointCompare(aValidator.publicKey, bValidator.publicKey)
: -aValidator.votes.cmp(bValidator.votes))
.map((validator) => common.ecPointToHex(validator.publicKey)), numValidators));
const standbyValidatorsArray = [...standbyValidatorsSet];
for (let i = 0; i < standbyValidatorsArray.length && validatorsPublicKeySet.size < numValidators; i += 1) {
validatorsPublicKeySet.add(standbyValidatorsArray[i]);
}
const validatorsPublicKeys = [...validatorsPublicKeySet].map((hex) => common.hexToECPoint(hex));
return validatorsPublicKeys.sort((aKey, bKey) => common.ecPointCompare(aKey, bKey));
};
//# sourceMappingURL=getValidators.js.map