UNPKG

@neo-one/node-blockchain-esnext-cjs

Version:

NEO•ONE NEO blockchain implementation.

765 lines (762 loc) 148 kB
"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,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIldyaXRlQmF0Y2hCbG9ja2NoYWluLnRzIl0sIm5hbWVzIjpbIldyaXRlQmF0Y2hCbG9ja2NoYWluIiwib3B0aW9ucyIsInNldHRpbmdzIiwiY3VycmVudEJsb2NrSW50ZXJuYWwiLCJjdXJyZW50QmxvY2siLCJjdXJyZW50SGVhZGVySW50ZXJuYWwiLCJjdXJyZW50SGVhZGVyIiwibXV0YWJsZVN0b3JhZ2UiLCJzdG9yYWdlIiwidm0iLCJnZXRWYWxpZGF0b3JzIiwib3V0cHV0IiwiT3V0cHV0U3RvcmFnZUNhY2hlIiwiY2FjaGVzIiwiYWNjb3VudCIsIlJlYWRBbGxBZGRVcGRhdGVEZWxldGVTdG9yYWdlQ2FjaGUiLCJuYW1lIiwicmVhZEFsbFN0b3JhZ2UiLCJ1cGRhdGUiLCJ2YWx1ZSIsImdldEtleUZyb21WYWx1ZSIsImhhc2giLCJnZXRLZXlTdHJpbmciLCJrZXkiLCJjb21tb24iLCJ1SW50MTYwVG9TdHJpbmciLCJjcmVhdGVBZGRDaGFuZ2UiLCJ0eXBlIiwiY3JlYXRlRGVsZXRlQ2hhbmdlIiwiYWNjb3VudFVuc3BlbnQiLCJSZWFkR2V0QWxsQWRkRGVsZXRlU3RvcmFnZUNhY2hlIiwicmVhZEdldEFsbFN0b3JhZ2UiLCJpbnB1dCIsInVJbnQyNTZUb1N0cmluZyIsImluZGV4IiwibWF0Y2hlc1BhcnRpYWxLZXkiLCJ1SW50MTYwRXF1YWwiLCJhY2NvdW50VW5jbGFpbWVkIiwiYWN0aW9uIiwiUmVhZEdldEFsbEFkZFN0b3JhZ2VDYWNoZSIsInRvU3RyaW5nIiwiaW5kZXhTdGFydCIsInVuZGVmaW5lZCIsImd0ZSIsImluZGV4U3RvcCIsImx0ZSIsImFzc2V0IiwiUmVhZEFkZFVwZGF0ZVN0b3JhZ2VDYWNoZSIsInJlYWRTdG9yYWdlIiwiYmxvY2siLCJCbG9ja0xpa2VTdG9yYWdlQ2FjaGUiLCJnZXQiLCJ0cnlHZXQiLCJibG9ja0RhdGEiLCJSZWFkQWRkU3RvcmFnZUNhY2hlIiwiaGVhZGVyIiwidHJhbnNhY3Rpb24iLCJvbkFkZCIsIlByb21pc2UiLCJhbGwiLCJvdXRwdXRzIiwibWFwIiwib3V0IiwiYWRkIiwidHJhbnNhY3Rpb25EYXRhIiwiY29udHJhY3QiLCJSZWFkQWRkRGVsZXRlU3RvcmFnZUNhY2hlIiwic3RvcmFnZUl0ZW0iLCJSZWFkR2V0QWxsQWRkVXBkYXRlRGVsZXRlU3RvcmFnZUNhY2hlIiwicHJlZml4IiwiZXZlcnkiLCJieXRlIiwiaWR4IiwidmFsaWRhdG9yIiwicHVibGljS2V5IiwiZWNQb2ludFRvU3RyaW5nIiwiaW52b2NhdGlvbkRhdGEiLCJ2YWxpZGF0b3JzQ291bnQiLCJSZWFkQWRkVXBkYXRlTWV0YWRhdGFTdG9yYWdlQ2FjaGUiLCJzZXRTdG9yYWdlIiwiR2VuZXNpc0Jsb2NrTm90UmVnaXN0ZXJlZEVycm9yIiwiY3VycmVudEJsb2NrSW5kZXgiLCJnZXRDaGFuZ2VTZXQiLCJPYmplY3QiLCJ2YWx1ZXMiLCJyZWR1Y2UiLCJhY2MiLCJjYWNoZSIsImNvbmNhdCIsInBlcnNpc3RCbG9jayIsIm1vbml0b3JJbiIsIm1vbml0b3IiLCJhdCIsIndpdGhEYXRhIiwibGFiZWxzIiwiTkVPX0JMT0NLX0lOREVYIiwibWF5YmVQcmV2QmxvY2tEYXRhIiwib3V0cHV0Q29udHJhY3RzTGlzdCIsImNhcHR1cmVTcGFuIiwicmVzb2x2ZSIsInByZXZpb3VzSGFzaCIsIlNldCIsInRyYW5zYWN0aW9ucyIsImFkZHJlc3MiLCJzdHJpbmdUb1VJbnQxNjAiLCJwcmV2QmxvY2tEYXRhIiwibGFzdEdsb2JhbFRyYW5zYWN0aW9uSW5kZXgiLCJ1dGlscyIsIk5FR0FUSVZFX09ORSIsImxhc3RHbG9iYWxBY3Rpb25JbmRleCIsInN5c3RlbUZlZSIsIlpFUk8iLCJvdXRwdXRDb250cmFjdHMiLCJmaWx0ZXIiLCJjb21tb25VdGlscyIsIm5vdE51bGwiLCJmb3JFYWNoIiwib3V0cHV0Q29udHJhY3QiLCJoYXNoSGV4IiwidXR4byIsInJlc3QiLCJUcmFuc2FjdGlvblR5cGUiLCJDbGFpbSIsIkNsYWltVHJhbnNhY3Rpb24iLCJDb250cmFjdCIsIkNvbnRyYWN0VHJhbnNhY3Rpb24iLCJNaW5lciIsIk1pbmVyVHJhbnNhY3Rpb24iLCJzb21lIiwiZ2xvYmFsQWN0aW9uSW5kZXgiLCJzcGFuIiwibGVuZ3RoIiwicGVyc2lzdFRyYW5zYWN0aW9ucyIsInBlcnNpc3RVVFhPVHJhbnNhY3Rpb25zIiwiQmxvY2tEYXRhIiwiQk4iLCJnZXRTeXN0ZW1GZWUiLCJnZXRPdXRwdXQiLCJnb3Zlcm5pbmdUb2tlbiIsInV0aWxpdHlUb2tlbiIsImZlZXMiLCJyZWdpc3RlclZhbGlkYXRvckZlZSIsImlucHV0cyIsImNsYWltcyIsIm91dHB1dFdpdGhJbnB1dHMiLCJpZHhBbmRUcmFuc2FjdGlvbiIsInB1c2giLCJnZXRPdXRwdXRXaXRoSW5wdXQiLCJUcmFuc2FjdGlvbkRhdGEiLCJzdGFydEhlaWdodCIsImJsb2NrSGFzaCIsImdsb2JhbEluZGV4IiwidXBkYXRlQWNjb3VudHMiLCJ1cGRhdGVDb2lucyIsIk9ORSIsInBlcnNpc3RUcmFuc2FjdGlvbiIsInN1YiIsInRyYW5zYWN0aW9uSW4iLCJ0cmFuc2FjdGlvbkluZGV4IiwiZ2xvYmFsQWN0aW9uSW5kZXhJbiIsIndpdGhMYWJlbHMiLCJORU9fVFJBTlNBQ1RJT05fVFlQRSIsIk5FT19UUkFOU0FDVElPTl9IQVNIIiwiYWNjb3VudENoYW5nZXMiLCJ2YWxpZGF0b3JDaGFuZ2VzIiwidmFsaWRhdG9yc0NvdW50Q2hhbmdlcyIsIlN0YXRlIiwiU3RhdGVUcmFuc2FjdGlvbiIsImdldERlc2NyaXB0b3JDaGFuZ2VzIiwiZ2V0QWNjb3VudCIsInRoZW4iLCJBY2NvdW50IiwiZ292ZXJuaW5nVG9rZW5IYXNoIiwicHJvY2Vzc1N0YXRlVHJhbnNhY3Rpb24iLCJSZWdpc3RlciIsIlJlZ2lzdGVyVHJhbnNhY3Rpb24iLCJBc3NldCIsImFtb3VudCIsInByZWNpc2lvbiIsIm93bmVyIiwiYWRtaW4iLCJpc3N1ZXIiLCJleHBpcmF0aW9uIiwiaXNGcm96ZW4iLCJJc3N1ZSIsIklzc3VlVHJhbnNhY3Rpb24iLCJyZXN1bHRzIiwiZW50cmllcyIsImdldFRyYW5zYWN0aW9uUmVzdWx0cyIsImFzc2V0SGV4Iiwic3RyaW5nVG9VSW50MjU2IiwiYXZhaWxhYmxlIiwibmVnIiwiRW5yb2xsbWVudCIsIkVucm9sbG1lbnRUcmFuc2FjdGlvbiIsIlZhbGlkYXRvciIsIlB1Ymxpc2giLCJQdWJsaXNoVHJhbnNhY3Rpb24iLCJJbnZvY2F0aW9uIiwiSW52b2NhdGlvblRyYW5zYWN0aW9uIiwidGVtcG9yYXJ5QmxvY2tjaGFpbiIsIm1pZ3JhdGVkQ29udHJhY3RIYXNoZXMiLCJ2b3RlVXBkYXRlcyIsImFjdGlvbnMiLCJyZXN1bHQiLCJ3cmFwRXhlY3V0ZVNjcmlwdHMiLCJleGVjdXRlU2NyaXB0cyIsInNjcmlwdHMiLCJjb2RlIiwic2NyaXB0IiwiYmxvY2tjaGFpbiIsInNjcmlwdENvbnRhaW5lciIsIlNjcmlwdENvbnRhaW5lclR5cGUiLCJUcmFuc2FjdGlvbiIsInRyaWdnZXJUeXBlIiwiVHJpZ2dlclR5cGUiLCJBcHBsaWNhdGlvbiIsImJsb2NrSW5kZXgiLCJ0cmFuc2FjdGlvbkhhc2giLCJnYXMiLCJsaXN0ZW5lcnMiLCJvbkxvZyIsIm1lc3NhZ2UiLCJzY3JpcHRIYXNoIiwiTG9nQWN0aW9uIiwib25Ob3RpZnkiLCJhcmdzIiwiTm90aWZpY2F0aW9uQWN0aW9uIiwib25NaWdyYXRlQ29udHJhY3QiLCJmcm9tIiwidG8iLCJvblNldFZvdGVzIiwidm90ZXMiLCJwZXJzaXN0aW5nQmxvY2siLCJhZGRBY3Rpb25zUHJvbWlzZSIsIkludm9jYXRpb25SZXN1bHRTdWNjZXNzIiwiYXNzZXRDaGFuZ2VTZXQiLCJhc3NldEhhc2giLCJjaGFuZ2UiLCJmaW5kIiwiY29udHJhY3RzQ2hhbmdlU2V0IiwiY29udHJhY3RIYXNoZXMiLCJkZWxldGVkQ29udHJhY3RIYXNoZXMiLCJkZWxldGUiLCJJbnZvY2F0aW9uRGF0YSIsImFjdGlvbkluZGV4U3RhcnQiLCJhY3Rpb25JbmRleFN0b3AiLCJ2YWxpZGF0b3JzQ291bnRWb3RlcyIsInB1YmxpY0tleUhleCIsInJlZ2lzdGVyZWQiLCJoZXhUb0VDUG9pbnQiLCJlcSIsIlZhbGlkYXRvcnNDb3VudCIsImlucHV0T3V0cHV0cyIsImNsYWltT3V0cHV0cyIsImdldElucHV0T3V0cHV0cyIsImFkZHJlc3NWYWx1ZXMiLCJ1SW50MTYwVG9IZXgiLCJhZGRyZXNzU3BlbnQiLCJncm91cEJ5QWRkcmVzcyIsImFkZHJlc3NDbGFpbWVkIiwiYWRkcmVzc091dHB1dHMiLCJzcGVudCIsImNsYWltZWQiLCJvdXRzIiwiY2hhbmdlcyIsInVwZGF0ZUFjY291bnQiLCJoZXhUb1VJbnQxNjAiLCJfYWRkcmVzcyIsIklucHV0IiwiYmFsYW5jZXMiLCJ1SW50MjU2VG9IZXgiLCJwcm9taXNlcyIsIkFjY291bnRVbnNwZW50IiwidUludDI1NkVxdWFsIiwiQWNjb3VudFVuY2xhaW1lZCIsIm5ld0FjY291bnQiLCJpc0RlbGV0YWJsZSIsImlucHV0Q2xhaW1zIiwiaGFzaElucHV0Q2xhaW1zIiwidXBkYXRlQ29pbiIsImhleFRvVUludDI1NiIsInNwZW50Q29pbnMiLCJlbmRIZWlnaHRzIiwiaW5wdXRDbGFpbSJdLCJtYXBwaW5ncyI6Ijs7O0FBQUEseUVBQTBDO0FBQzFDLHFFQUFzQztBQUN0Qyx5RUFBMEM7QUFGMUMsc0RBQUE7QUFDQSxzREFBQTtBQW1EQSxrREFBQTtBQWlCQSwwQ0FBQTtBQUNBLGlDQUFBO0FBRUEscUNBQUE7QUFDQSxtREFBQTtBQUNBLGlEQUFBO0FBWUEsNkRBQUE7QUFrREEsTUFBYUEsb0JBQU47SUEwQ0wsWUFBbUJDLE9BQW5CO1FBQ0UsSUFBQSxDQUFLQyxRQUFMLEdBQWdCRCxPQUFBQSxDQUFRQyxRQUF4QixDQUFBO1FBQ0EsSUFBQSxDQUFLQyxvQkFBTCxHQUE0QkYsT0FBQUEsQ0FBUUcsWUFBcEMsQ0FBQTtRQUNBLElBQUEsQ0FBS0MscUJBQUwsR0FBNkJKLE9BQUFBLENBQVFLLGFBQXJDLENBQUE7UUFDQSxJQUFBLENBQUtDLGNBQUwsR0FBc0JOLE9BQUFBLENBQVFPLE9BQTlCLENBQUE7UUFDQSxJQUFBLENBQUtDLEVBQUwsR0FBVVIsT0FBQUEsQ0FBUVEsRUFBbEIsQ0FBQTtRQUNBLElBQUEsQ0FBS0MsYUFBTCxHQUFxQlQsT0FBQUEsQ0FBUVMsYUFBN0IsQ0FBQTtRQUVBLE1BQU1DLE1BQUFBLEdBQVMsSUFBSUMsaUNBQUosQ0FBdUIsR0FBQSxFQUFBLENBQU0sSUFBQSxDQUFLSixPQUFMLENBQWFHLE1BQTFDLENBQWYsQ0FBQTtRQUNBLElBQUEsQ0FBS0UsTUFBTCxHQUFjO1lBQ1pDLE9BQUFBLEVBQVMsSUFBSUMsaURBQUosQ0FBdUM7Z0JBQzlDQyxJQUFBQSxFQUFNLFNBRHdDO2dCQUU5Q0MsY0FBQUEsRUFBZ0IsR0FBQSxFQUFBLENBQU0sSUFBQSxDQUFLVCxPQUFMLENBQWFNLE9BRlc7Z0JBRzlDSSxNQUFBQSxFQUFRLENBQUNDLEtBQUQsRUFBUUQsTUFBUixFQUFBLEVBQUEsQ0FBbUJDLEtBQUFBLENBQU1ELE1BQU4sQ0FBYUEsTUFBYixDQUhtQjtnQkFJOUNFLGVBQUFBLEVBQWtCRCxLQUFELENBQUEsRUFBQSxDQUFBLENBQVk7b0JBQUVFLElBQUFBLEVBQU1GLEtBQUFBLENBQU1FLElBQUFBO2lCQUExQixDQUo2QjtnQkFLOUNDLFlBQUFBLEVBQWVDLEdBQUQsQ0FBQSxFQUFBLENBQVNDLG9CQUFBQSxDQUFPQyxlQUFQLENBQXVCRixHQUFBQSxDQUFJRixJQUEzQixDQUx1QjtnQkFNOUNLLGVBQUFBLEVBQWtCUCxLQUFELENBQUEsRUFBQSxDQUFBLENBQVk7b0JBQUVRLElBQUFBLEVBQU0sU0FBUjtvQkFBbUJSLEtBQUFBO2lCQUEvQixDQU42QjtnQkFPOUNTLGtCQUFBQSxFQUFxQkwsR0FBRCxDQUFBLEVBQUEsQ0FBQSxDQUFVO29CQUFFSSxJQUFBQSxFQUFNLFNBQVI7b0JBQW1CSixHQUFBQTtpQkFBN0IsQ0FBQTthQVBiLENBREc7WUFVWk0sY0FBQUEsRUFBZ0IsSUFBSUMsOENBQUosQ0FBb0M7Z0JBQ2xEZCxJQUFBQSxFQUFNLGdCQUQ0QztnQkFFbERlLGlCQUFBQSxFQUFtQixHQUFBLEVBQUEsQ0FBTSxJQUFBLENBQUt2QixPQUFMLENBQWFxQixjQUZZO2dCQUdsRFQsZUFBQUEsRUFBa0JELEtBQUQsQ0FBQSxFQUFBLENBQUEsQ0FBWTtvQkFBRUUsSUFBQUEsRUFBTUYsS0FBQUEsQ0FBTUUsSUFBZDtvQkFBb0JXLEtBQUFBLEVBQU9iLEtBQUFBLENBQU1hLEtBQUFBO2lCQUE3QyxDQUhpQztnQkFJbERWLFlBQUFBLEVBQWVDLEdBQUQsQ0FBQSxFQUFBLENBQ1gsR0FBRUMsb0JBQUFBLENBQU9DLGVBQVAsQ0FBdUJGLEdBQUFBLENBQUlGLElBQTNCLENBQWlDLElBQUdHLG9CQUFBQSxDQUFPUyxlQUFQLENBQXVCVixHQUFBQSxDQUFJUyxLQUFKLENBQVVYLElBQWpDLENBQXVDLElBQUdFLEdBQUFBLENBQUlTLEtBQUosQ0FBVUUsS0FBTSxFQUxqRDtnQkFNbERDLGlCQUFBQSxFQUFtQixDQUFDaEIsS0FBRCxFQUFRSSxHQUFSLEVBQUEsRUFBQSxDQUFnQkMsb0JBQUFBLENBQU9ZLFlBQVAsQ0FBb0JqQixLQUFBQSxDQUFNRSxJQUExQixFQUFnQ0UsR0FBQUEsQ0FBSUYsSUFBcEMsQ0FOZTtnQkFPbERLLGVBQUFBLEVBQWtCUCxLQUFELENBQUEsRUFBQSxDQUFBLENBQVk7b0JBQUVRLElBQUFBLEVBQU0sZ0JBQVI7b0JBQTBCUixLQUFBQTtpQkFBdEMsQ0FQaUM7Z0JBUWxEUyxrQkFBQUEsRUFBcUJMLEdBQUQsQ0FBQSxFQUFBLENBQUEsQ0FBVTtvQkFBRUksSUFBQUEsRUFBTSxnQkFBUjtvQkFBMEJKLEdBQUFBO2lCQUFwQyxDQUFBO2FBUk4sQ0FWSjtZQW9CWmMsZ0JBQUFBLEVBQWtCLElBQUlQLDhDQUFKLENBQW9DO2dCQUNwRGQsSUFBQUEsRUFBTSxrQkFEOEM7Z0JBRXBEZSxpQkFBQUEsRUFBbUIsR0FBQSxFQUFBLENBQU0sSUFBQSxDQUFLdkIsT0FBTCxDQUFhNkIsZ0JBRmM7Z0JBR3BEakIsZUFBQUEsRUFBa0JELEtBQUQsQ0FBQSxFQUFBLENBQUEsQ0FBWTtvQkFBRUUsSUFBQUEsRUFBTUYsS0FBQUEsQ0FBTUUsSUFBZDtvQkFBb0JXLEtBQUFBLEVBQU9iLEtBQUFBLENBQU1hLEtBQUFBO2lCQUE3QyxDQUhtQztnQkFJcERWLFlBQUFBLEVBQWVDLEdBQUQsQ0FBQSxFQUFBLENBQ1gsR0FBRUMsb0JBQUFBLENBQU9DLGVBQVAsQ0FBdUJGLEdBQUFBLENBQUlGLElBQTNCLENBQWlDLElBQUdHLG9CQUFBQSxDQUFPUyxlQUFQLENBQXVCVixHQUFBQSxDQUFJUyxLQUFKLENBQVVYLElBQWpDLENBQXVDLElBQUdFLEdBQUFBLENBQUlTLEtBQUosQ0FBVUUsS0FBTSxFQUwvQztnQkFNcERDLGlCQUFBQSxFQUFtQixDQUFDaEIsS0FBRCxFQUFRSSxHQUFSLEVBQUEsRUFBQSxDQUFnQkMsb0JBQUFBLENBQU9ZLFlBQVAsQ0FBb0JqQixLQUFBQSxDQUFNRSxJQUExQixFQUFnQ0UsR0FBQUEsQ0FBSUYsSUFBcEMsQ0FOaUI7Z0JBT3BESyxlQUFBQSxFQUFrQlAsS0FBRCxDQUFBLEVBQUEsQ0FBQSxDQUFZO29CQUFFUSxJQUFBQSxFQUFNLGtCQUFSO29CQUE0QlIsS0FBQUE7aUJBQXhDLENBUG1DO2dCQVFwRFMsa0JBQUFBLEVBQXFCTCxHQUFELENBQUEsRUFBQSxDQUFBLENBQVU7b0JBQUVJLElBQUFBLEVBQU0sa0JBQVI7b0JBQTRCSixHQUFBQTtpQkFBdEMsQ0FBQTthQVJKLENBcEJOO1lBOEJaZSxNQUFBQSxFQUFRLElBQUlDLHdDQUFKLENBQThCO2dCQUNwQ3ZCLElBQUFBLEVBQU0sUUFEOEI7Z0JBRXBDZSxpQkFBQUEsRUFBbUIsR0FBQSxFQUFBLENBQU0sSUFBQSxDQUFLdkIsT0FBTCxDQUFhOEIsTUFGRjtnQkFHcENsQixlQUFBQSxFQUFrQkQsS0FBRCxDQUFBLEVBQUEsQ0FBQSxDQUFZO29CQUMzQmUsS0FBQUEsRUFBT2YsS0FBQUEsQ0FBTWUsS0FBQUE7aUJBREUsQ0FIbUI7Z0JBTXBDWixZQUFBQSxFQUFlQyxHQUFELENBQUEsRUFBQSxDQUFTQSxHQUFBQSxDQUFJVyxLQUFKLENBQVVNLFFBQVYsQ0FBbUIsRUFBbkIsQ0FOYTtnQkFPcENMLGlCQUFBQSxFQUFtQixDQUFDaEIsS0FBRCxFQUFRSSxHQUFSLEVBQUEsRUFBQSxDQUNqQixDQUFDQSxHQUFBQSxDQUFJa0IsVUFBSixLQUFtQkMsU0FBbkIsSUFBZ0N2QixLQUFBQSxDQUFNZSxLQUFOLENBQVlTLEdBQVosQ0FBZ0JwQixHQUFBQSxDQUFJa0IsVUFBcEIsQ0FBakMsQ0FBQSxJQUFBLENBQ0NsQixHQUFBQSxDQUFJcUIsU0FBSixLQUFrQkYsU0FBbEIsSUFBK0J2QixLQUFBQSxDQUFNZSxLQUFOLENBQVlXLEdBQVosQ0FBZ0J0QixHQUFBQSxDQUFJcUIsU0FBcEIsQ0FEaEMsQ0FSa0M7Z0JBVXBDbEIsZUFBQUEsRUFBa0JQLEtBQUQsQ0FBQSxFQUFBLENBQUEsQ0FBWTtvQkFBRVEsSUFBQUEsRUFBTSxRQUFSO29CQUFrQlIsS0FBQUE7aUJBQTlCLENBQUE7YUFWWCxDQTlCSTtZQTBDWjJCLEtBQUFBLEVBQU8sSUFBSUMsd0NBQUosQ0FBOEI7Z0JBQ25DL0IsSUFBQUEsRUFBTSxPQUQ2QjtnQkFFbkNnQyxXQUFBQSxFQUFhLEdBQUEsRUFBQSxDQUFNLElBQUEsQ0FBS3hDLE9BQUwsQ0FBYXNDLEtBRkc7Z0JBR25DNUIsTUFBQUEsRUFBUSxDQUFDQyxLQUFELEVBQVFELE1BQVIsRUFBQSxFQUFBLENBQW1CQyxLQUFBQSxDQUFNRCxNQUFOLENBQWFBLE1BQWIsQ0FIUTtnQkFJbkNFLGVBQUFBLEVBQWtCRCxLQUFELENBQUEsRUFBQSxDQUFBLENBQVk7b0JBQUVFLElBQUFBLEVBQU1GLEtBQUFBLENBQU1FLElBQUFBO2lCQUExQixDQUprQjtnQkFLbkNDLFlBQUFBLEVBQWVDLEdBQUQsQ0FBQSxFQUFBLENBQVNDLG9CQUFBQSxDQUFPUyxlQUFQLENBQXVCVixHQUFBQSxDQUFJRixJQUEzQixDQUxZO2dCQU1uQ0ssZUFBQUEsRUFBa0JQLEtBQUQsQ0FBQSxFQUFBLENBQUEsQ0FBWTtvQkFBRVEsSUFBQUEsRUFBTSxPQUFSO29CQUFpQlIsS0FBQUE7aUJBQTdCLENBQUE7YUFOWixDQTFDSztZQWtEWjhCLEtBQUFBLEVBQU8sSUFBSUMsb0NBQUosQ0FBMEI7Z0JBQy9CbEMsSUFBQUEsRUFBTSxPQUR5QjtnQkFFL0JnQyxXQUFBQSxFQUFhLEdBQUEsRUFBQSxDQUFBLENBQU87b0JBQ2xCRyxHQUFBQSxFQUFLLEl