@neo-one/node-blockchain-esnext-cjs
Version:
NEO•ONE NEO blockchain implementation.
765 lines (762 loc) • 148 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const mapValues_1 = tslib_1.__importDefault(require("lodash/mapValues"));
const groupBy_1 = tslib_1.__importDefault(require("lodash/groupBy"));
const partition_1 = tslib_1.__importDefault(require("lodash/partition"));
// tslint:disable no-array-mutation no-object-mutation
const client_core_1 = require("@neo-one/client-core-esnext-cjs");
const node_core_1 = require("@neo-one/node-core-esnext-cjs");
const utils_1 = require("@neo-one/utils-esnext-cjs");
const bn_js_1 = require("bn.js");
const errors_1 = require("./errors");
const getValidators_1 = require("./getValidators");
const StorageCache_1 = require("./StorageCache");
const wrapExecuteScripts_1 = require("./wrapExecuteScripts");
class WriteBatchBlockchain {
constructor(options) {
this.settings = options.settings;
this.currentBlockInternal = options.currentBlock;
this.currentHeaderInternal = options.currentHeader;
this.mutableStorage = options.storage;
this.vm = options.vm;
this.getValidators = options.getValidators;
const output = new StorageCache_1.OutputStorageCache(() => this.storage.output);
this.caches = {
account: new StorageCache_1.ReadAllAddUpdateDeleteStorageCache({
name: 'account',
readAllStorage: () => this.storage.account,
update: (value, update) => value.update(update),
getKeyFromValue: value => ({
hash: value.hash
}),
getKeyString: key => client_core_1.common.uInt160ToString(key.hash),
createAddChange: value => ({
type: 'account',
value
}),
createDeleteChange: key => ({
type: 'account',
key
})
}),
accountUnspent: new StorageCache_1.ReadGetAllAddDeleteStorageCache({
name: 'accountUnspent',
readGetAllStorage: () => this.storage.accountUnspent,
getKeyFromValue: value => ({
hash: value.hash,
input: value.input
}),
getKeyString: key => `${client_core_1.common.uInt160ToString(key.hash)}:${client_core_1.common.uInt256ToString(key.input.hash)}:${key.input.index}`,
matchesPartialKey: (value, key) => client_core_1.common.uInt160Equal(value.hash, key.hash),
createAddChange: value => ({
type: 'accountUnspent',
value
}),
createDeleteChange: key => ({
type: 'accountUnspent',
key
})
}),
accountUnclaimed: new StorageCache_1.ReadGetAllAddDeleteStorageCache({
name: 'accountUnclaimed',
readGetAllStorage: () => this.storage.accountUnclaimed,
getKeyFromValue: value => ({
hash: value.hash,
input: value.input
}),
getKeyString: key => `${client_core_1.common.uInt160ToString(key.hash)}:${client_core_1.common.uInt256ToString(key.input.hash)}:${key.input.index}`,
matchesPartialKey: (value, key) => client_core_1.common.uInt160Equal(value.hash, key.hash),
createAddChange: value => ({
type: 'accountUnclaimed',
value
}),
createDeleteChange: key => ({
type: 'accountUnclaimed',
key
})
}),
action: new StorageCache_1.ReadGetAllAddStorageCache({
name: 'action',
readGetAllStorage: () => this.storage.action,
getKeyFromValue: value => ({
index: value.index
}),
getKeyString: key => key.index.toString(10),
matchesPartialKey: (value, key) => (key.indexStart === undefined || value.index.gte(key.indexStart)) && (key.indexStop === undefined || value.index.lte(key.indexStop)),
createAddChange: value => ({
type: 'action',
value
})
}),
asset: new StorageCache_1.ReadAddUpdateStorageCache({
name: 'asset',
readStorage: () => this.storage.asset,
update: (value, update) => value.update(update),
getKeyFromValue: value => ({
hash: value.hash
}),
getKeyString: key => client_core_1.common.uInt256ToString(key.hash),
createAddChange: value => ({
type: 'asset',
value
})
}),
block: new StorageCache_1.BlockLikeStorageCache({
name: 'block',
readStorage: () => ({
get: this.storage.block.get,
tryGet: this.storage.block.tryGet
}),
createAddChange: value => ({
type: 'block',
value
})
}),
blockData: new StorageCache_1.ReadAddStorageCache({
name: 'blockData',
readStorage: () => this.storage.blockData,
getKeyFromValue: value => ({
hash: value.hash
}),
getKeyString: key => client_core_1.common.uInt256ToString(key.hash),
createAddChange: value => ({
type: 'blockData',
value
})
}),
header: new StorageCache_1.BlockLikeStorageCache({
name: 'header',
readStorage: () => ({
get: this.storage.header.get,
tryGet: this.storage.header.tryGet
}),
createAddChange: value => ({
type: 'header',
value
})
}),
transaction: new StorageCache_1.ReadAddStorageCache({
name: 'transaction',
readStorage: () => this.storage.transaction,
getKeyFromValue: value => ({
hash: value.hash
}),
getKeyString: key => client_core_1.common.uInt256ToString(key.hash),
createAddChange: value => ({
type: 'transaction',
value
}),
onAdd: async (value) => {
await Promise.all(value.outputs.map(async (out, index) => output.add({
hash: value.hash,
index,
output: out
})));
}
}),
transactionData: new StorageCache_1.ReadAddUpdateStorageCache({
name: 'transactionData',
readStorage: () => this.storage.transactionData,
update: (value, update) => value.update(update),
getKeyFromValue: value => ({
hash: value.hash
}),
getKeyString: key => client_core_1.common.uInt256ToString(key.hash),
createAddChange: value => ({
type: 'transactionData',
value
})
}),
output,
contract: new StorageCache_1.ReadAddDeleteStorageCache({
name: 'contract',
readStorage: () => this.storage.contract,
getKeyFromValue: value => ({
hash: value.hash
}),
getKeyString: key => client_core_1.common.uInt160ToString(key.hash),
createAddChange: value => ({
type: 'contract',
value
}),
createDeleteChange: key => ({
type: 'contract',
key
})
}),
storageItem: new StorageCache_1.ReadGetAllAddUpdateDeleteStorageCache({
name: 'storageItem',
readGetAllStorage: () => this.storage.storageItem,
update: (value, update) => value.update(update),
getKeyFromValue: value => ({
hash: value.hash,
key: value.key
}),
getKeyString: key => `${client_core_1.common.uInt160ToString(key.hash)}:${key.key.toString('hex')}`,
matchesPartialKey: (value, key) => (key.hash === undefined || client_core_1.common.uInt160Equal(value.hash, key.hash)) && (key.prefix === undefined || key.prefix.every((byte, idx) => value.key[idx] === byte)),
createAddChange: value => ({
type: 'storageItem',
value
}),
createDeleteChange: key => ({
type: 'storageItem',
key
})
}),
validator: new StorageCache_1.ReadAllAddUpdateDeleteStorageCache({
name: 'validator',
readAllStorage: () => this.storage.validator,
getKeyFromValue: value => ({
publicKey: value.publicKey
}),
getKeyString: key => client_core_1.common.ecPointToString(key.publicKey),
createAddChange: value => ({
type: 'validator',
value
}),
update: (value, update) => value.update(update),
createDeleteChange: key => ({
type: 'validator',
key
})
}),
invocationData: new StorageCache_1.ReadAddStorageCache({
name: 'invocationData',
readStorage: () => this.storage.invocationData,
getKeyFromValue: value => ({
hash: value.hash
}),
getKeyString: key => client_core_1.common.uInt256ToString(key.hash),
createAddChange: value => ({
type: 'invocationData',
value
})
}),
validatorsCount: new StorageCache_1.ReadAddUpdateMetadataStorageCache({
name: 'validatorsCount',
readStorage: () => this.storage.validatorsCount,
createAddChange: value => ({
type: 'validatorsCount',
value
}),
update: (value, update) => value.update(update)
})
};
this.account = this.caches.account;
this.accountUnspent = this.caches.accountUnspent;
this.accountUnclaimed = this.caches.accountUnclaimed;
this.action = this.caches.action;
this.asset = this.caches.asset;
this.block = this.caches.block;
this.blockData = this.caches.blockData;
this.header = this.caches.header;
this.transaction = this.caches.transaction;
this.transactionData = this.caches.transactionData;
this.output = this.caches.output;
this.contract = this.caches.contract;
this.storageItem = this.caches.storageItem;
this.validator = this.caches.validator;
this.invocationData = this.caches.invocationData;
this.validatorsCount = this.caches.validatorsCount;
}
get storage() {
return this.mutableStorage;
}
setStorage(storage) {
this.mutableStorage = storage;
}
get currentBlock() {
if (this.currentBlockInternal === undefined) {
throw new errors_1.GenesisBlockNotRegisteredError();
}
return this.currentBlockInternal;
}
get currentBlockIndex() {
return this.currentBlockInternal === undefined ? 0 : this.currentBlockInternal.index;
}
get currentHeader() {
if (this.currentHeaderInternal === undefined) {
throw new errors_1.GenesisBlockNotRegisteredError();
}
return this.currentHeaderInternal;
}
getChangeSet() {
return Object.values(this.caches).reduce((acc, cache) => acc.concat(cache.getChangeSet()), []);
}
async persistBlock(monitorIn, block) {
const monitor = monitorIn.at('write_blockchain').withData({
[utils_1.labels.NEO_BLOCK_INDEX]: block.index
});
const [maybePrevBlockData, outputContractsList] = await monitor.captureSpan(async () => Promise.all([block.index === 0 ? Promise.resolve(undefined) : this.blockData.get({
hash: block.previousHash
}), Promise.all([...new Set(block.transactions.reduce((acc, transaction) => acc.concat(transaction.outputs.map(output => client_core_1.common.uInt160ToString(output.address))), []))].map(async (hash) => this.contract.tryGet({
hash: client_core_1.common.stringToUInt160(hash)
}))), this.block.add(block), this.header.add(block.header)]), {
name: 'neo_write_blockchain_stage_0'
});
const prevBlockData = maybePrevBlockData === undefined ? {
lastGlobalTransactionIndex: client_core_1.utils.NEGATIVE_ONE,
lastGlobalActionIndex: client_core_1.utils.NEGATIVE_ONE,
systemFee: client_core_1.utils.ZERO
} : {
lastGlobalTransactionIndex: maybePrevBlockData.lastGlobalTransactionIndex,
lastGlobalActionIndex: maybePrevBlockData.lastGlobalActionIndex,
systemFee: maybePrevBlockData.systemFee
};
const outputContracts = {};
outputContractsList.filter(utils_1.utils.notNull).forEach(outputContract => {
outputContracts[outputContract.hashHex] = outputContract;
});
const [utxo, rest] = partition_1.default(block.transactions.map((transaction, idx) => [idx, transaction]), // tslint:disable-next-line no-unused
([idx, transaction]) => (transaction.type === client_core_1.TransactionType.Claim && transaction instanceof client_core_1.ClaimTransaction || transaction.type === client_core_1.TransactionType.Contract && transaction instanceof client_core_1.ContractTransaction || transaction.type === client_core_1.TransactionType.Miner && transaction instanceof client_core_1.MinerTransaction) && !transaction.outputs.some(output => outputContracts[client_core_1.common.uInt160ToString(output.address)] !== undefined));
const [globalActionIndex] = await monitor.captureSpan(async (span) => Promise.all([rest.length > 0 ? this.persistTransactions(span, block, rest, prevBlockData.lastGlobalTransactionIndex, prevBlockData.lastGlobalActionIndex) : Promise.resolve(prevBlockData.lastGlobalActionIndex), utxo.length > 0 ? // tslint:disable-next-line no-any
this.persistUTXOTransactions(span, block, utxo, prevBlockData.lastGlobalTransactionIndex) : Promise.resolve()]), {
name: 'neo_write_blockchain_stage_1'
});
await monitor.captureSpan(async () => this.blockData.add(new node_core_1.BlockData({
hash: block.hash,
lastGlobalTransactionIndex: prevBlockData.lastGlobalTransactionIndex.add(new bn_js_1.BN(block.transactions.length)),
lastGlobalActionIndex: globalActionIndex,
systemFee: prevBlockData.systemFee.add(block.getSystemFee({
getOutput: this.output.get,
governingToken: this.settings.governingToken,
utilityToken: this.settings.utilityToken,
fees: this.settings.fees,
registerValidatorFee: this.settings.registerValidatorFee
}))
})), {
name: 'neo_write_blockchain_stage_2'
});
}
async persistUTXOTransactions(monitor, block, transactions, lastGlobalTransactionIndex) {
await monitor.captureSpan(async (span) => {
const inputs = [];
const claims = [];
const outputWithInputs = []; // tslint:disable-next-line no-unused no-loop-statement no-dead-store
for (const idxAndTransaction of transactions) {
const transaction = idxAndTransaction[1];
inputs.push(...transaction.inputs);
if (transaction.type === client_core_1.TransactionType.Claim && transaction instanceof client_core_1.ClaimTransaction) {
claims.push(...transaction.claims);
}
outputWithInputs.push(...this.getOutputWithInput(transaction));
}
await Promise.all([Promise.all(// tslint:disable-next-line no-unused
transactions.map(async ([idx, transaction]) => this.transaction.add(transaction, true))), Promise.all(transactions.map(async ([idx, transaction]) => this.transactionData.add(new client_core_1.TransactionData({
hash: transaction.hash,
startHeight: block.index,
blockHash: block.hash,
index: idx,
globalIndex: lastGlobalTransactionIndex.add(new bn_js_1.BN(idx + 1))
}), true))), this.updateAccounts(span, inputs, claims, outputWithInputs), this.updateCoins(span, inputs, claims, block)]);
}, {
name: 'neo_write_blockchain_persist_utxo_transactions'
});
}
async persistTransactions(monitor, block, transactions, lastGlobalTransactionIndex, lastGlobalActionIndex) {
return monitor.captureSpan(async (span) => {
let globalActionIndex = lastGlobalActionIndex.add(client_core_1.utils.ONE); // tslint:disable-next-line no-loop-statement
for (const [idx, transaction] of transactions) {
globalActionIndex = await this.persistTransaction(span, block, transaction, idx, lastGlobalTransactionIndex, globalActionIndex);
}
return globalActionIndex.sub(client_core_1.utils.ONE);
}, {
name: 'neo_write_blockchain_persist_transactions'
});
}
async persistTransaction(monitor, block, transactionIn, transactionIndex, lastGlobalTransactionIndex, globalActionIndexIn) {
let globalActionIndex = globalActionIndexIn;
await monitor.withLabels({
[utils_1.labels.NEO_TRANSACTION_TYPE]: transactionIn.type
}).withData({
[utils_1.labels.NEO_TRANSACTION_HASH]: transactionIn.hashHex
}).captureSpan(async (span) => {
const transaction = transactionIn;
const claims = transaction.type === client_core_1.TransactionType.Claim && transaction instanceof client_core_1.ClaimTransaction ? transaction.claims : [];
let accountChanges = {};
let validatorChanges = {};
let validatorsCountChanges = [];
if (transaction.type === client_core_1.TransactionType.State && transaction instanceof client_core_1.StateTransaction) {
({
accountChanges,
validatorChanges,
validatorsCountChanges
} = await getValidators_1.getDescriptorChanges({
transactions: [transaction],
getAccount: async (hash) => this.account.tryGet({
hash
}).then(account => account === undefined ? new client_core_1.Account({
hash
}) : account),
governingTokenHash: this.settings.governingToken.hashHex
}));
}
await Promise.all([this.transaction.add(transaction, true), this.transactionData.add(new client_core_1.TransactionData({
hash: transaction.hash,
blockHash: block.hash,
startHeight: block.index,
index: transactionIndex,
globalIndex: lastGlobalTransactionIndex.add(new bn_js_1.BN(transactionIndex + 1))
}), true), this.updateAccounts(span, transaction.inputs, claims, this.getOutputWithInput(transaction), accountChanges), this.updateCoins(span, transaction.inputs, claims, block), this.processStateTransaction(span, validatorChanges, validatorsCountChanges)]);
if (transaction.type === client_core_1.TransactionType.Register && transaction instanceof client_core_1.RegisterTransaction) {
await this.asset.add(new client_core_1.Asset({
hash: transaction.hash,
type: transaction.asset.type,
name: transaction.asset.name,
amount: transaction.asset.amount,
precision: transaction.asset.precision,
owner: transaction.asset.owner,
admin: transaction.asset.admin,
issuer: transaction.asset.admin,
expiration: this.currentBlockIndex + 2 * 2000000,
isFrozen: false
}));
}
else if (transaction.type === client_core_1.TransactionType.Issue && transaction instanceof client_core_1.IssueTransaction) {
const results = await Promise.all(Object.entries(transaction.getTransactionResults({
getOutput: this.output.get
})));
await Promise.all(results.map(async ([assetHex, value]) => {
const hash = client_core_1.common.stringToUInt256(assetHex);
const asset = await this.asset.get({
hash
});
await this.asset.update(asset, {
available: asset.available.add(value.neg())
});
}));
}
else if (transaction.type === client_core_1.TransactionType.Enrollment && transaction instanceof client_core_1.EnrollmentTransaction) {
await this.validator.add(new client_core_1.Validator({
publicKey: transaction.publicKey
}));
}
else if (transaction.type === client_core_1.TransactionType.Publish && transaction instanceof client_core_1.PublishTransaction) {
const contract = await this.contract.tryGet({
hash: transaction.contract.hash
});
if (contract === undefined) {
await this.contract.add(transaction.contract);
}
}
else if (transaction.type === client_core_1.TransactionType.Invocation && transaction instanceof client_core_1.InvocationTransaction) {
const temporaryBlockchain = new WriteBatchBlockchain({
settings: this.settings,
currentBlock: this.currentBlockInternal,
currentHeader: this.currentHeader,
// tslint:disable-next-line no-any
storage: this,
vm: this.vm,
getValidators: this.getValidators
});
const migratedContractHashes = [];
const voteUpdates = [];
const actions = [];
const result = await wrapExecuteScripts_1.wrapExecuteScripts(async () => this.vm.executeScripts({
monitor: span,
scripts: [{
code: transaction.script
}],
blockchain: temporaryBlockchain,
scriptContainer: {
type: client_core_1.ScriptContainerType.Transaction,
value: transaction
},
triggerType: node_core_1.TriggerType.Application,
action: {
blockIndex: block.index,
blockHash: block.hash,
transactionIndex,
transactionHash: transaction.hash
},
gas: transaction.gas,
listeners: {
onLog: ({ message, scriptHash }) => {
actions.push(new client_core_1.LogAction({
index: globalActionIndex,
scriptHash,
message
}));
globalActionIndex = globalActionIndex.add(client_core_1.utils.ONE);
},
onNotify: ({ args, scriptHash }) => {
actions.push(new client_core_1.NotificationAction({
index: globalActionIndex,
scriptHash,
args
}));
globalActionIndex = globalActionIndex.add(client_core_1.utils.ONE);
},
onMigrateContract: ({ from, to }) => {
migratedContractHashes.push([from, to]);
},
onSetVotes: ({ address, votes }) => {
voteUpdates.push([address, votes]);
}
},
persistingBlock: block
}));
const addActionsPromise = Promise.all(actions.map(async (action) => this.action.add(action)));
if (result instanceof client_core_1.InvocationResultSuccess) {
const assetChangeSet = temporaryBlockchain.asset.getChangeSet();
const assetHash = assetChangeSet.map(change => change.type === 'add' && change.change.type === 'asset' ? change.change.value.hash : undefined).find(value => value !== undefined);
const contractsChangeSet = temporaryBlockchain.contract.getChangeSet();
const contractHashes = contractsChangeSet.map(change => change.type === 'add' && change.change.type === 'contract' ? change.change.value.hash : undefined).filter(utils_1.utils.notNull);
const deletedContractHashes = contractsChangeSet.map(change => change.type === 'delete' && change.change.type === 'contract' ? change.change.key.hash : undefined).filter(utils_1.utils.notNull);
await Promise.all([Promise.all(temporaryBlockchain.getChangeSet().map(async (change) => {
if (change.type === 'add') {
// tslint:disable-next-line no-any
await this.caches[change.change.type].add(change.change.value, true);
}
else if (change.type === 'delete') {
// tslint:disable-next-line no-any
await this.caches[change.change.type].delete(change.change.key);
}
})), this.invocationData.add(new client_core_1.InvocationData({
hash: transaction.hash,
assetHash,
contractHashes,
deletedContractHashes,
migratedContractHashes,
voteUpdates,
blockIndex: block.index,
transactionIndex,
actionIndexStart: globalActionIndexIn,
actionIndexStop: globalActionIndex,
result
})), addActionsPromise]);
}
else {
await Promise.all([this.invocationData.add(new client_core_1.InvocationData({
hash: transaction.hash,
assetHash: undefined,
contractHashes: [],
deletedContractHashes: [],
migratedContractHashes: [],
voteUpdates: [],
blockIndex: block.index,
transactionIndex,
actionIndexStart: globalActionIndexIn,
actionIndexStop: globalActionIndex,
result
})), addActionsPromise]);
}
}
}, {
name: 'neo_write_blockchain_persist_single_transaction'
});
return globalActionIndex;
}
async processStateTransaction(monitor, validatorChanges, validatorsCountChanges) {
await monitor.captureSpan(async () => {
const validatorsCount = await this.validatorsCount.tryGet();
const validatorsCountVotes = validatorsCount === undefined ? [] : [...validatorsCount.votes]; // tslint:disable-next-line no-loop-statement
for (const [index, value] of validatorsCountChanges.entries()) {
validatorsCountVotes[index] = value;
}
await Promise.all([Promise.all(Object.entries(validatorChanges).map(async ([publicKeyHex, { registered, votes }]) => {
const publicKey = client_core_1.common.hexToECPoint(publicKeyHex);
const validator = await this.validator.tryGet({
publicKey
});
if (validator === undefined) {
await this.validator.add(new client_core_1.Validator({
publicKey,
registered,
votes
}));
}
else if ((registered !== undefined && !registered || registered === undefined && !validator.registered) && (votes !== undefined && votes.eq(client_core_1.utils.ZERO) || votes === undefined && validator.votes.eq(client_core_1.utils.ZERO))) {
await this.validator.delete({
publicKey: validator.publicKey
});
}
else {
await this.validator.update(validator, {
votes,
registered
});
}
})), validatorsCount === undefined ? this.validatorsCount.add(new node_core_1.ValidatorsCount({
votes: validatorsCountVotes
})) : (async () => {
await this.validatorsCount.update(validatorsCount, {
votes: validatorsCountVotes
});
})()]);
}, {
name: 'neo_write_blockchain_process_state_transaction'
});
}
async updateAccounts(monitor, inputs, claims, outputs, accountChanges = {}) {
const [inputOutputs, claimOutputs] = await monitor.captureSpan(async () => Promise.all([this.getInputOutputs(inputs), this.getInputOutputs(claims)]), {
name: 'neo_write_blockchain_update_accounts_get_input_outputs'
});
await monitor.captureSpan(async () => {
const addressValues = Object.entries(groupBy_1.default(inputOutputs.map(({ output }) => [output.address, output.asset, output.value.neg()]).concat(outputs.map(({ output }) => [output.address, output.asset, output.value])), ([address]) => client_core_1.common.uInt160ToHex(address)));
const addressSpent = this.groupByAddress(inputOutputs);
const addressClaimed = mapValues_1.default(this.groupByAddress(claimOutputs), values => values.map(({ input }) => input));
const addressOutputs = groupBy_1.default(outputs, output => client_core_1.common.uInt160ToHex(output.output.address));
await Promise.all(addressValues.map(async ([address, values]) => {
const spent = addressSpent[address];
const claimed = addressClaimed[address];
const outs = addressOutputs[address];
const changes = accountChanges[address];
await this.updateAccount(client_core_1.common.hexToUInt160(address), // tslint:disable-next-line no-unused
values.map(([_address, asset, value]) => [asset, value]), spent === undefined ? [] : spent, claimed === undefined ? [] : claimed, outs === undefined ? [] : outs, changes === undefined ? [] : changes);
}));
}, {
name: 'neo_write_blockchain_update_accounts_process'
});
}
getOutputWithInput(transaction) {
return transaction.outputs.map((output, index) => ({
output,
input: new client_core_1.Input({
hash: transaction.hash,
index
})
}));
}
async getInputOutputs(inputs) {
return Promise.all(inputs.map(async (input) => {
const output = await this.output.get(input);
return {
input,
output
};
}));
}
groupByAddress(inputOutputs) {
return groupBy_1.default(inputOutputs, ({ output }) => client_core_1.common.uInt160ToHex(output.address));
}
async updateAccount(address, values, spent, claimed, outputs, votes) {
const account = await this.account.tryGet({
hash: address
});
const balances = values.reduce((acc, [asset, value]) => {
const key = client_core_1.common.uInt256ToHex(asset);
if (acc[key] === undefined) {
acc[key] = client_core_1.utils.ZERO;
}
acc[key] = acc[key].add(value);
return acc;
}, account === undefined ? {} : { ...account.balances
});
const promises = [];
promises.push(...spent.map(async ({ input }) => this.accountUnspent.delete({
hash: address,
input
})));
promises.push(...outputs.map(async ({ input }) => this.accountUnspent.add(new node_core_1.AccountUnspent({
hash: address,
input
}))));
promises.push(...claimed.map(async (input) => this.accountUnclaimed.delete({
hash: address,
input
})));
promises.push(...spent.filter(({ output }) => client_core_1.common.uInt256Equal(output.asset, this.settings.governingToken.hash)).map(async ({ input }) => this.accountUnclaimed.add(new node_core_1.AccountUnclaimed({
hash: address,
input
}))));
if (account === undefined) {
promises.push(this.account.add(new client_core_1.Account({
hash: address,
balances,
votes
})));
}
else {
promises.push(this.account.update(account, {
balances,
votes
}).then(async (newAccount) => {
if (newAccount.isDeletable()) {
await this.account.delete({
hash: address
});
}
}));
}
await Promise.all(promises);
}
async updateCoins(monitor, inputs, claims, block) {
await monitor.captureSpan(async () => {
const inputClaims = inputs.map(input => ({
type: 'input',
input,
hash: input.hash
})).concat(claims.map(input => ({
type: 'claim',
input,
hash: input.hash
})));
const hashInputClaims = Object.entries(groupBy_1.default(inputClaims, ({ hash }) => client_core_1.common.uInt256ToHex(hash)));
await Promise.all(hashInputClaims.map(async ([hash, values]) => this.updateCoin(client_core_1.common.hexToUInt256(hash), values, block)));
}, {
name: 'neo_write_blockchain_update_coins'
});
}
async updateCoin(hash, inputClaims, block) {
const spentCoins = await this.transactionData.get({
hash
});
const endHeights = { ...spentCoins.endHeights
};
const claimed = { ...spentCoins.claimed
}; // tslint:disable-next-line no-loop-statement
for (const inputClaim of inputClaims) {
if (inputClaim.type === 'input') {
endHeights[inputClaim.input.index] = block.index;
}
else {
claimed[inputClaim.input.index] = true;
}
}
await this.transactionData.update(spentCoins, {
endHeights,
claimed
});
}
}
exports.WriteBatchBlockchain = WriteBatchBlockchain;
/*
Possibly broken on TestNet:
if (
block.index !== 31331 && // Just seems like a bad script - unknown op
block.index !== 62024 && // Invalid order for Account arguments
block.index !== 131854 && // Calls contract without storage
block.index !== 163432 && // Calls contract without storage
block.index !== 163446 && // Calls contract without storage
block.index !== 163457 && // Calls contract without storage
block.index !== 163470 && // Calls contract without storage
block.index !== 163484 && // Calls contract without storage
block.index !== 163491 && // Calls contract without storage
block.index !== 163512 && // Calls contract without storage
block.index !== 460363 && // PICKITEM on non-array.
block.index !== 460376 && // PICKITEM on non-array.
block.index !== 460393 && // PICKITEM on non-array.
block.index !== 460410 && // PICKITEM on non-array.
block.index !== 561159 && // Bug in contract code - no inputs for transaction
block.index !== 568381 && // Bug in contract code - no inputs for transaction
block.index !== 572375 && // Bug in contract code - no inputs for transaction
block.index !== 608107 && // Unknown OP 0xDB (219)
block.index !== 608111 && // Unknown OP 0xDB (219)
block.index !== 608135 && // Unknown OP 0x70 (112)
block.index !== 609278 && // Unknown OP 0x70 (112)
block.index !== 609402 && // Unknown OP 0x70 (112)
block.index !== 609408 && // Unknown OP 0x70 (112)
block.index !== 609504 &&
block.index !== 609513 && // Unknown op: 0x70 (112)
block.index !== 637192 && // Seems like a bad argument to CheckWitness
!error.message.includes('Unknown op: 112') &&
!error.message.includes('Script execution threw an Error')
) {
console.log(block.index);
console.error(error);
throw error;
}
*/
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIldyaXRlQmF0Y2hCbG9ja2NoYWluLnRzIl0sIm5hbWVzIjpbIldyaXRlQmF0Y2hCbG9ja2NoYWluIiwib3B0aW9ucyIsInNldHRpbmdzIiwiY3VycmVudEJsb2NrSW50ZXJuYWwiLCJjdXJyZW50QmxvY2siLCJjdXJyZW50SGVhZGVySW50ZXJuYWwiLCJjdXJyZW50SGVhZGVyIiwibXV0YWJsZVN0b3JhZ2UiLCJzdG9yYWdlIiwidm0iLCJnZXRWYWxpZGF0b3JzIiwib3V0cHV0IiwiT3V0cHV0U3RvcmFnZUNhY2hlIiwiY2FjaGVzIiwiYWNjb3VudCIsIlJlYWRBbGxBZGRVcGRhdGVEZWxldGVTdG9yYWdlQ2FjaGUiLCJuYW1lIiwicmVhZEFsbFN0b3JhZ2UiLCJ1cGRhdGUiLCJ2YWx1ZSIsImdldEtleUZyb21WYWx1ZSIsImhhc2giLCJnZXRLZXlTdHJpbmciLCJrZXkiLCJjb21tb24iLCJ1SW50MTYwVG9TdHJpbmciLCJjcmVhdGVBZGRDaGFuZ2UiLCJ0eXBlIiwiY3JlYXRlRGVsZXRlQ2hhbmdlIiwiYWNjb3VudFVuc3BlbnQiLCJSZWFkR2V0QWxsQWRkRGVsZXRlU3RvcmFnZUNhY2hlIiwicmVhZEdldEFsbFN0b3JhZ2UiLCJpbnB1dCIsInVJbnQyNTZUb1N0cmluZyIsImluZGV4IiwibWF0Y2hlc1BhcnRpYWxLZXkiLCJ1SW50MTYwRXF1YWwiLCJhY2NvdW50VW5jbGFpbWVkIiwiYWN0aW9uIiwiUmVhZEdldEFsbEFkZFN0b3JhZ2VDYWNoZSIsInRvU3RyaW5nIiwiaW5kZXhTdGFydCIsInVuZGVmaW5lZCIsImd0ZSIsImluZGV4U3RvcCIsImx0ZSIsImFzc2V0IiwiUmVhZEFkZFVwZGF0ZVN0b3JhZ2VDYWNoZSIsInJlYWRTdG9yYWdlIiwiYmxvY2siLCJCbG9ja0xpa2VTdG9yYWdlQ2FjaGUiLCJnZXQiLCJ0cnlHZXQiLCJibG9ja0RhdGEiLCJSZWFkQWRkU3RvcmFnZUNhY2hlIiwiaGVhZGVyIiwidHJhbnNhY3Rpb24iLCJvbkFkZCIsIlByb21pc2UiLCJhbGwiLCJvdXRwdXRzIiwibWFwIiwib3V0IiwiYWRkIiwidHJhbnNhY3Rpb25EYXRhIiwiY29udHJhY3QiLCJSZWFkQWRkRGVsZXRlU3RvcmFnZUNhY2hlIiwic3RvcmFnZUl0ZW0iLCJSZWFkR2V0QWxsQWRkVXBkYXRlRGVsZXRlU3RvcmFnZUNhY2hlIiwicHJlZml4IiwiZXZlcnkiLCJieXRlIiwiaWR4IiwidmFsaWRhdG9yIiwicHVibGljS2V5IiwiZWNQb2ludFRvU3RyaW5nIiwiaW52b2NhdGlvbkRhdGEiLCJ2YWxpZGF0b3JzQ291bnQiLCJSZWFkQWRkVXBkYXRlTWV0YWRhdGFTdG9yYWdlQ2FjaGUiLCJzZXRTdG9yYWdlIiwiR2VuZXNpc0Jsb2NrTm90UmVnaXN0ZXJlZEVycm9yIiwiY3VycmVudEJsb2NrSW5kZXgiLCJnZXRDaGFuZ2VTZXQiLCJPYmplY3QiLCJ2YWx1ZXMiLCJyZWR1Y2UiLCJhY2MiLCJjYWNoZSIsImNvbmNhdCIsInBlcnNpc3RCbG9jayIsIm1vbml0b3JJbiIsIm1vbml0b3IiLCJhdCIsIndpdGhEYXRhIiwibGFiZWxzIiwiTkVPX0JMT0NLX0lOREVYIiwibWF5YmVQcmV2QmxvY2tEYXRhIiwib3V0cHV0Q29udHJhY3RzTGlzdCIsImNhcHR1cmVTcGFuIiwicmVzb2x2ZSIsInByZXZpb3VzSGFzaCIsIlNldCIsInRyYW5zYWN0aW9ucyIsImFkZHJlc3MiLCJzdHJpbmdUb1VJbnQxNjAiLCJwcmV2QmxvY2tEYXRhIiwibGFzdEdsb2JhbFRyYW5zYWN0aW9uSW5kZXgiLCJ1dGlscyIsIk5FR0FUSVZFX09ORSIsImxhc3RHbG9iYWxBY3Rpb25JbmRleCIsInN5c3RlbUZlZSIsIlpFUk8iLCJvdXRwdXRDb250cmFjdHMiLCJmaWx0ZXIiLCJjb21tb25VdGlscyIsIm5vdE51bGwiLCJmb3JFYWNoIiwib3V0cHV0Q29udHJhY3QiLCJoYXNoSGV4IiwidXR4byIsInJlc3QiLCJUcmFuc2FjdGlvblR5cGUiLCJDbGFpbSIsIkNsYWltVHJhbnNhY3Rpb24iLCJDb250cmFjdCIsIkNvbnRyYWN0VHJhbnNhY3Rpb24iLCJNaW5lciIsIk1pbmVyVHJhbnNhY3Rpb24iLCJzb21lIiwiZ2xvYmFsQWN0aW9uSW5kZXgiLCJzcGFuIiwibGVuZ3RoIiwicGVyc2lzdFRyYW5zYWN0aW9ucyIsInBlcnNpc3RVVFhPVHJhbnNhY3Rpb25zIiwiQmxvY2tEYXRhIiwiQk4iLCJnZXRTeXN0ZW1GZWUiLCJnZXRPdXRwdXQiLCJnb3Zlcm5pbmdUb2tlbiIsInV0aWxpdHlUb2tlbiIsImZlZXMiLCJyZWdpc3RlclZhbGlkYXRvckZlZSIsImlucHV0cyIsImNsYWltcyIsIm91dHB1dFdpdGhJbnB1dHMiLCJpZHhBbmRUcmFuc2FjdGlvbiIsInB1c2giLCJnZXRPdXRwdXRXaXRoSW5wdXQiLCJUcmFuc2FjdGlvbkRhdGEiLCJzdGFydEhlaWdodCIsImJsb2NrSGFzaCIsImdsb2JhbEluZGV4IiwidXBkYXRlQWNjb3VudHMiLCJ1cGRhdGVDb2lucyIsIk9ORSIsInBlcnNpc3RUcmFuc2FjdGlvbiIsInN1YiIsInRyYW5zYWN0aW9uSW4iLCJ0cmFuc2FjdGlvbkluZGV4IiwiZ2xvYmFsQWN0aW9uSW5kZXhJbiIsIndpdGhMYWJlbHMiLCJORU9fVFJBTlNBQ1RJT05fVFlQRSIsIk5FT19UUkFOU0FDVElPTl9IQVNIIiwiYWNjb3VudENoYW5nZXMiLCJ2YWxpZGF0b3JDaGFuZ2VzIiwidmFsaWRhdG9yc0NvdW50Q2hhbmdlcyIsIlN0YXRlIiwiU3RhdGVUcmFuc2FjdGlvbiIsImdldERlc2NyaXB0b3JDaGFuZ2VzIiwiZ2V0QWNjb3VudCIsInRoZW4iLCJBY2NvdW50IiwiZ292ZXJuaW5nVG9rZW5IYXNoIiwicHJvY2Vzc1N0YXRlVHJhbnNhY3Rpb24iLCJSZWdpc3RlciIsIlJlZ2lzdGVyVHJhbnNhY3Rpb24iLCJBc3NldCIsImFtb3VudCIsInByZWNpc2lvbiIsIm93bmVyIiwiYWRtaW4iLCJpc3N1ZXIiLCJleHBpcmF0aW9uIiwiaXNGcm96ZW4iLCJJc3N1ZSIsIklzc3VlVHJhbnNhY3Rpb24iLCJyZXN1bHRzIiwiZW50cmllcyIsImdldFRyYW5zYWN0aW9uUmVzdWx0cyIsImFzc2V0SGV4Iiwic3RyaW5nVG9VSW50MjU2IiwiYXZhaWxhYmxlIiwibmVnIiwiRW5yb2xsbWVudCIsIkVucm9sbG1lbnRUcmFuc2FjdGlvbiIsIlZhbGlkYXRvciIsIlB1Ymxpc2giLCJQdWJsaXNoVHJhbnNhY3Rpb24iLCJJbnZvY2F0aW9uIiwiSW52b2NhdGlvblRyYW5zYWN0aW9uIiwidGVtcG9yYXJ5QmxvY2tjaGFpbiIsIm1pZ3JhdGVkQ29udHJhY3RIYXNoZXMiLCJ2b3RlVXBkYXRlcyIsImFjdGlvbnMiLCJyZXN1bHQiLCJ3cmFwRXhlY3V0ZVNjcmlwdHMiLCJleGVjdXRlU2NyaXB0cyIsInNjcmlwdHMiLCJjb2RlIiwic2NyaXB0IiwiYmxvY2tjaGFpbiIsInNjcmlwdENvbnRhaW5lciIsIlNjcmlwdENvbnRhaW5lclR5cGUiLCJUcmFuc2FjdGlvbiIsInRyaWdnZXJUeXBlIiwiVHJpZ2dlclR5cGUiLCJBcHBsaWNhdGlvbiIsImJsb2NrSW5kZXgiLCJ0cmFuc2FjdGlvbkhhc2giLCJnYXMiLCJsaXN0ZW5lcnMiLCJvbkxvZyIsIm1lc3NhZ2UiLCJzY3JpcHRIYXNoIiwiTG9nQWN0aW9uIiwib25Ob3RpZnkiLCJhcmdzIiwiTm90aWZpY2F0aW9uQWN0aW9uIiwib25NaWdyYXRlQ29udHJhY3QiLCJmcm9tIiwidG8iLCJvblNldFZvdGVzIiwidm90ZXMiLCJwZXJzaXN0aW5nQmxvY2siLCJhZGRBY3Rpb25zUHJvbWlzZSIsIkludm9jYXRpb25SZXN1bHRTdWNjZXNzIiwiYXNzZXRDaGFuZ2VTZXQiLCJhc3NldEhhc2giLCJjaGFuZ2UiLCJmaW5kIiwiY29udHJhY3RzQ2hhbmdlU2V0IiwiY29udHJhY3RIYXNoZXMiLCJkZWxldGVkQ29udHJhY3RIYXNoZXMiLCJkZWxldGUiLCJJbnZvY2F0aW9uRGF0YSIsImFjdGlvbkluZGV4U3RhcnQiLCJhY3Rpb25JbmRleFN0b3AiLCJ2YWxpZGF0b3JzQ291bnRWb3RlcyIsInB1YmxpY0tleUhleCIsInJlZ2lzdGVyZWQiLCJoZXhUb0VDUG9pbnQiLCJlcSIsIlZhbGlkYXRvcnNDb3VudCIsImlucHV0T3V0cHV0cyIsImNsYWltT3V0cHV0cyIsImdldElucHV0T3V0cHV0cyIsImFkZHJlc3NWYWx1ZXMiLCJ1SW50MTYwVG9IZXgiLCJhZGRyZXNzU3BlbnQiLCJncm91cEJ5QWRkcmVzcyIsImFkZHJlc3NDbGFpbWVkIiwiYWRkcmVzc091dHB1dHMiLCJzcGVudCIsImNsYWltZWQiLCJvdXRzIiwiY2hhbmdlcyIsInVwZGF0ZUFjY291bnQiLCJoZXhUb1VJbnQxNjAiLCJfYWRkcmVzcyIsIklucHV0IiwiYmFsYW5jZXMiLCJ1SW50MjU2VG9IZXgiLCJwcm9taXNlcyIsIkFjY291bnRVbnNwZW50IiwidUludDI1NkVxdWFsIiwiQWNjb3VudFVuY2xhaW1lZCIsIm5ld0FjY291bnQiLCJpc0RlbGV0YWJsZSIsImlucHV0Q2xhaW1zIiwiaGFzaElucHV0Q2xhaW1zIiwidXBkYXRlQ29pbiIsImhleFRvVUludDI1NiIsInNwZW50Q29pbnMiLCJlbmRIZWlnaHRzIiwiaW5wdXRDbGFpbSJdLCJtYXBwaW5ncyI6Ijs7O0FBQUEseUVBQTBDO0FBQzFDLHFFQUFzQztBQUN0Qyx5RUFBMEM7QUFGMUMsc0RBQUE7QUFDQSxzREFBQTtBQW1EQSxrREFBQTtBQWlCQSwwQ0FBQTtBQUNBLGlDQUFBO0FBRUEscUNBQUE7QUFDQSxtREFBQTtBQUNBLGlEQUFBO0FBWUEsNkRBQUE7QUFrREEsTUFBYUEsb0JBQU47SUEwQ0wsWUFBbUJDLE9BQW5CO1FBQ0UsSUFBQSxDQUFLQyxRQUFMLEdBQWdCRCxPQUFBQSxDQUFRQyxRQUF4QixDQUFBO1FBQ0EsSUFBQSxDQUFLQyxvQkFBTCxHQUE0QkYsT0FBQUEsQ0FBUUcsWUFBcEMsQ0FBQTtRQUNBLElBQUEsQ0FBS0MscUJBQUwsR0FBNkJKLE9BQUFBLENBQVFLLGFBQXJDLENBQUE7UUFDQSxJQUFBLENBQUtDLGNBQUwsR0FBc0JOLE9BQUFBLENBQVFPLE9B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