UNPKG

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

Version:

NEO•ONE NEO blockchain implementation.

678 lines (677 loc) 110 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const client_core_1 = require("@neo-one/client-core-esnext-cjs"); const monitor_1 = require("@neo-one/monitor-esnext-cjs"); const node_core_1 = require("@neo-one/node-core-esnext-cjs"); const utils_1 = require("@neo-one/utils-esnext-cjs"); const js_priority_queue_1 = tslib_1.__importDefault(require("js-priority-queue")); const BehaviorSubject_1 = require("rxjs/internal/BehaviorSubject"); const Subject_1 = require("rxjs/internal/Subject"); const toArray_1 = require("rxjs/internal/operators/toArray"); const errors_1 = require("./errors"); const getValidators_1 = require("./getValidators"); const wrapExecuteScripts_1 = require("./wrapExecuteScripts"); const WriteBatchBlockchain_1 = require("./WriteBatchBlockchain"); const NAMESPACE = 'blockchain'; const NEO_BLOCKCHAIN_PERSIST_BLOCK_DURATION_SECONDS = monitor_1.metrics.createHistogram({ name: 'neo_blockchain_persist_block_duration_seconds' }); const NEO_BLOCKCHAIN_PERSIST_BLOCK_FAILURES_TOTAL = monitor_1.metrics.createCounter({ name: 'neo_blockchain_persist_block_failures_total' }); const NEO_BLOCKCHAIN_BLOCK_INDEX_GAUGE = monitor_1.metrics.createGauge({ name: 'neo_blockchain_block_index', help: 'The current block index' }); const NEO_BLOCKCHAIN_PERSISTING_BLOCK_INDEX_GAUGE = monitor_1.metrics.createGauge({ name: 'neo_blockchain_persisting_block_index', help: 'The current in progress persist index' }); const NEO_BLOCKCHAIN_PERSIST_BLOCK_LATENCY_SECONDS = monitor_1.metrics.createHistogram({ name: 'neo_blockchain_persist_block_latency_seconds', help: 'The latency from block timestamp to persist', buckets: [1, 2, 5, 7.5, 10, 12.5, 15, 17.5, 20] }); class Blockchain { constructor(options) { this.mutablePersistingBlocks = false; this.mutableBlockQueue = new js_priority_queue_1.default({ comparator: (a, b) => a.block.index - b.block.index }); this.mutableInQueue = new Set(); this.mutableRunning = false; this.mutableBlock$ = new Subject_1.Subject(); this.getValidators = async (transactions, monitor) => this.getMonitor(monitor).captureSpanLog(async () => getValidators_1.getValidators(this, transactions), { name: 'neo_blockchain_get_validators', level: { log: 'verbose', span: 'info' } }); this.calculateClaimAmount = async (claims, monitor) => this.getMonitor(monitor).captureSpanLog(async () => { const spentCoins = await Promise.all(claims.map(async (claim) => this.tryGetSpentCoin(claim))); const filteredSpentCoins = spentCoins.filter(utils_1.utils.notNull); if (spentCoins.length !== filteredSpentCoins.length) { throw new errors_1.CoinUnspentError(); } if (filteredSpentCoins.some(coin => coin.claimed)) { throw new errors_1.CoinClaimedError(); } if (filteredSpentCoins.some(coin => !client_core_1.common.uInt256Equal(coin.output.asset, this.settings.governingToken.hash))) { throw new errors_1.InvalidClaimError(); } return client_core_1.utils.calculateClaimAmount({ coins: filteredSpentCoins.map(coin => ({ value: coin.output.value, startHeight: coin.startHeight, endHeight: coin.endHeight })), decrementInterval: this.settings.decrementInterval, generationAmount: this.settings.generationAmount, getSystemFee: async (index) => { const header = await this.header.get({ hashOrIndex: index }); const blockData = await this.blockData.get({ hash: header.hash }); return blockData.systemFee; } }); }, { name: 'neo_blockchain_calculate_claim_amount', level: { log: 'verbose', span: 'info' } }); this.verifyScript = async ({ scriptContainer, hash, witness }, monitor) => { let { verification } = witness; if (verification.length === 0) { const builder = new client_core_1.ScriptBuilder(); builder.emitAppCallVerification(hash); verification = builder.build(); } else if (!client_core_1.common.uInt160Equal(hash, client_core_1.crypto.toScriptHash(verification))) { throw new errors_1.WitnessVerifyError(); } const blockchain = this.createWriteBlockchain(); const result = await this.vm.executeScripts({ monitor: this.getMonitor(monitor), scripts: [{ code: witness.invocation, pushOnly: true }, { code: verification }], blockchain, scriptContainer, triggerType: node_core_1.TriggerType.Verification, action: node_core_1.NULL_ACTION, gas: client_core_1.utils.ZERO }); const { stack } = result; if (stack.length !== 1) { throw new errors_1.ScriptVerifyError(`Verification did not return one result. This may be a bug in the ` + `smart contract. Found ${stack.length} results.`); } const top = stack[0]; if (!top.asBoolean()) { throw new errors_1.ScriptVerifyError('Verification did not succeed.'); } }; this.tryGetInvocationData = async (transaction) => { const data = await this.invocationData.tryGet({ hash: transaction.hash }); if (data === undefined) { return undefined; } const [asset, contracts, actions] = await Promise.all([data.assetHash === undefined ? Promise.resolve(undefined) : this.asset.get({ hash: data.assetHash }), Promise.all(data.contractHashes.map(async (contractHash) => this.contract.tryGet({ hash: contractHash }))), data.actionIndexStart.eq(data.actionIndexStop) ? Promise.resolve([]) : this.action.getAll$({ indexStart: data.actionIndexStart, indexStop: data.actionIndexStop.sub(client_core_1.utils.ONE) }).pipe(toArray_1.toArray()).toPromise()]); return { asset, contracts: contracts.filter(utils_1.utils.notNull), deletedContractHashes: data.deletedContractHashes, migratedContractHashes: data.migratedContractHashes, voteUpdates: data.voteUpdates, result: data.result, actions }; }; this.tryGetTransactionData = async (transaction) => this.transactionData.tryGet({ hash: transaction.hash }); this.getUnclaimed = async (hash) => this.accountUnclaimed.getAll$({ hash }).pipe(toArray_1.toArray()).toPromise().then(values => values.map(value => value.input)); this.getUnspent = async (hash) => { const unspent = await this.accountUnspent.getAll$({ hash }).pipe(toArray_1.toArray()).toPromise(); return unspent.map(value => value.input); }; this.getAllValidators = async () => this.validator.all$.pipe(toArray_1.toArray()).toPromise(); this.isSpent = async (input) => { const transactionData = await this.transactionData.tryGet({ hash: input.hash }); return transactionData !== undefined && transactionData.endHeights[input.index] !== undefined; }; this.tryGetSpentCoin = async (input) => { const [transactionData, output] = await Promise.all([this.transactionData.tryGet({ hash: input.hash }), this.output.get(input)]); if (transactionData === undefined) { return undefined; } const endHeight = transactionData.endHeights[input.index]; if (endHeight === undefined) { return undefined; } const claimed = transactionData.claimed[input.index]; return { output, startHeight: transactionData.startHeight, endHeight, claimed: !!claimed }; }; this.storage = options.storage; this.mutableCurrentBlock = options.currentBlock; this.mutablePreviousBlock = options.previousBlock; this.mutableCurrentHeader = options.currentHeader; this.vm = options.vm; this.settings$ = new BehaviorSubject_1.BehaviorSubject(options.settings); this.monitor = options.monitor.at(NAMESPACE); NEO_BLOCKCHAIN_BLOCK_INDEX_GAUGE.set(this.currentBlockIndex); NEO_BLOCKCHAIN_PERSISTING_BLOCK_INDEX_GAUGE.set(this.currentBlockIndex); // tslint:disable-next-line no-this-assignment const self = this; this.deserializeWireContext = { get messageMagic() { return self.settings.messageMagic; } }; this.feeContext = { get getOutput() { return self.output.get; }, get governingToken() { return self.settings.governingToken; }, get utilityToken() { return self.settings.utilityToken; }, get fees() { return self.settings.fees; }, get registerValidatorFee() { return self.settings.registerValidatorFee; } }; this.serializeJSONContext = { get addressVersion() { return self.settings.addressVersion; }, get feeContext() { return self.feeContext; }, get tryGetInvocationData() { return self.tryGetInvocationData; }, get tryGetTransactionData() { return self.tryGetTransactionData; }, get getUnclaimed() { return self.getUnclaimed; }, get getUnspent() { return self.getUnspent; } }; this.mutableWriteBlockchain = this.createWriteBlockchain(); this.start(); } static async create({ settings, storage, vm, monitor }) { const [currentBlock, currentHeader] = await Promise.all([storage.block.tryGetLatest(), storage.header.tryGetLatest()]); let previousBlock; if (currentBlock !== undefined) { previousBlock = await storage.block.tryGet({ hashOrIndex: currentBlock.index - 1 }); } const blockchain = new Blockchain({ currentBlock, currentHeader, previousBlock, settings, storage, vm, monitor }); if (currentHeader === undefined) { await blockchain.persistHeaders([settings.genesisBlock.header]); } if (currentBlock === undefined) { await blockchain.persistBlock({ block: settings.genesisBlock }); } return blockchain; } get settings() { return this.settings$.getValue(); } get currentBlock() { if (this.mutableCurrentBlock === undefined) { throw new errors_1.GenesisBlockNotRegisteredError(); } return this.mutableCurrentBlock; } get previousBlock() { return this.mutablePreviousBlock; } get currentHeader() { if (this.mutableCurrentHeader === undefined) { throw new errors_1.GenesisBlockNotRegisteredError(); } return this.mutableCurrentHeader; } get currentBlockIndex() { return this.mutableCurrentBlock === undefined ? -1 : this.currentBlock.index; } get block$() { return this.mutableBlock$; } get isPersistingBlock() { return this.mutablePersistingBlocks; } get account() { return this.mutableWriteBlockchain === undefined ? this.storage.account : this.mutableWriteBlockchain.account; } get accountUnclaimed() { return this.mutableWriteBlockchain === undefined ? this.storage.accountUnclaimed : this.mutableWriteBlockchain.accountUnclaimed; } get accountUnspent() { return this.mutableWriteBlockchain === undefined ? this.storage.accountUnspent : this.mutableWriteBlockchain.accountUnspent; } get action() { return this.mutableWriteBlockchain === undefined ? this.storage.action : this.mutableWriteBlockchain.action; } get asset() { return this.mutableWriteBlockchain === undefined ? this.storage.asset : this.mutableWriteBlockchain.asset; } get block() { return this.mutableWriteBlockchain === undefined ? this.storage.block : this.mutableWriteBlockchain.block; } get blockData() { return this.mutableWriteBlockchain === undefined ? this.storage.blockData : this.mutableWriteBlockchain.blockData; } get header() { return this.mutableWriteBlockchain === undefined ? this.storage.header : this.mutableWriteBlockchain.header; } get transaction() { return this.mutableWriteBlockchain === undefined ? this.storage.transaction : this.mutableWriteBlockchain.transaction; } get transactionData() { return this.mutableWriteBlockchain === undefined ? this.storage.transactionData : this.mutableWriteBlockchain.transactionData; } get output() { return this.mutableWriteBlockchain === undefined ? this.storage.output : this.mutableWriteBlockchain.output; } get contract() { return this.mutableWriteBlockchain === undefined ? this.storage.contract : this.mutableWriteBlockchain.contract; } get storageItem() { return this.mutableWriteBlockchain === undefined ? this.storage.storageItem : this.mutableWriteBlockchain.storageItem; } get validator() { return this.mutableWriteBlockchain === undefined ? this.storage.validator : this.mutableWriteBlockchain.validator; } get invocationData() { return this.mutableWriteBlockchain === undefined ? this.storage.invocationData : this.mutableWriteBlockchain.invocationData; } get validatorsCount() { return this.mutableWriteBlockchain === undefined ? this.storage.validatorsCount : this.mutableWriteBlockchain.validatorsCount; } async stop() { if (!this.mutableRunning) { return; } if (this.mutablePersistingBlocks) { // tslint:disable-next-line promise-must-complete const doneRunningPromise = new Promise(resolve => { this.mutableDoneRunningResolve = resolve; }); this.mutableRunning = false; await doneRunningPromise; this.mutableDoneRunningResolve = undefined; } else { this.mutableRunning = false; } this.monitor.log({ name: 'neo_blockchain_stop' }); } updateSettings(settings) { this.settings$.next(settings); } async persistBlock({ monitor, block, unsafe = false }) { // tslint:disable-next-line promise-must-complete return new Promise((resolve, reject) => { if (this.mutableInQueue.has(block.hashHex)) { return; } this.mutableInQueue.add(block.hashHex); this.mutableBlockQueue.queue({ monitor: this.getMonitor(monitor), block, resolve, reject, unsafe }); // tslint:disable-next-line no-floating-promises this.persistBlocksAsync(); }); } async persistHeaders(_headers) { } async verifyBlock(block, monitor) { await this.getMonitor(monitor).withData({ [utils_1.labels.NEO_BLOCK_INDEX]: block.index }).captureSpan(async (span) => block.verify({ genesisBlock: this.settings.genesisBlock, tryGetBlock: this.block.tryGet, tryGetHeader: this.header.tryGet, isSpent: this.isSpent, getAsset: this.asset.get, getOutput: this.output.get, tryGetAccount: this.account.tryGet, getValidators: this.getValidators, standbyValidators: this.settings.standbyValidators, getAllValidators: this.getAllValidators, calculateClaimAmount: async (claims) => this.calculateClaimAmount(claims, span), verifyScript: async (options) => this.verifyScript(options, span), currentHeight: this.mutableCurrentBlock === undefined ? 0 : this.mutableCurrentBlock.index, governingToken: this.settings.governingToken, utilityToken: this.settings.utilityToken, fees: this.settings.fees, registerValidatorFee: this.settings.registerValidatorFee }), { name: 'neo_blockchain_verify_block' }); } async verifyConsensusPayload(payload, monitor) { await this.getMonitor(monitor).withData({ [utils_1.labels.NEO_CONSENSUS_HASH]: payload.hashHex }).captureSpan(async (span) => payload.verify({ getValidators: async () => this.getValidators([], span), verifyScript: async (options) => this.verifyScript(options, span), currentIndex: this.mutableCurrentBlock === undefined ? 0 : this.mutableCurrentBlock.index, currentBlockHash: this.currentBlock.hash }), { name: 'neo_blockchain_verify_consensus' }); } async verifyTransaction({ monitor, transaction, memPool }) { try { await this.getMonitor(monitor).withData({ [utils_1.labels.NEO_TRANSACTION_HASH]: transaction.hashHex }).captureSpan(async (span) => transaction.verify({ calculateClaimAmount: this.calculateClaimAmount, isSpent: this.isSpent, getAsset: this.asset.get, getOutput: this.output.get, tryGetAccount: this.account.tryGet, standbyValidators: this.settings.standbyValidators, getAllValidators: this.getAllValidators, verifyScript: async (options) => this.verifyScript(options, span), governingToken: this.settings.governingToken, utilityToken: this.settings.utilityToken, fees: this.settings.fees, registerValidatorFee: this.settings.registerValidatorFee, currentHeight: this.currentBlockIndex, memPool }), { name: 'neo_blockchain_verify_transaction' }); } catch (error) { if (error.code === undefined || typeof error.code !== 'string' || !error.code.includes('VERIFY')) { throw new errors_1.UnknownVerifyError(error.message); } throw error; } } async invokeScript(script, monitor) { const transaction = new client_core_1.InvocationTransaction({ script, gas: client_core_1.common.ONE_HUNDRED_FIXED8 }); return this.invokeTransaction(transaction, monitor); } async invokeTransaction(transaction, monitor) { const blockchain = this.createWriteBlockchain(); return wrapExecuteScripts_1.wrapExecuteScripts(async () => this.vm.executeScripts({ monitor: this.getMonitor(monitor), scripts: [{ code: transaction.script }], blockchain, scriptContainer: { type: client_core_1.ScriptContainerType.Transaction, value: transaction }, triggerType: node_core_1.TriggerType.Application, action: node_core_1.NULL_ACTION, gas: transaction.gas, skipWitnessVerify: true })); } async reset() { await this.stop(); await this.storage.reset(); this.mutableCurrentHeader = undefined; this.mutableCurrentBlock = undefined; this.mutablePreviousBlock = undefined; this.mutableWriteBlockchain = undefined; this.mutableWriteBlockchain = this.createWriteBlockchain(); this.start(); await this.persistHeaders([this.settings.genesisBlock.header]); await this.persistBlock({ block: this.settings.genesisBlock }); } async persistBlocksAsync() { if (this.mutablePersistingBlocks || !this.mutableRunning) { return; } this.mutablePersistingBlocks = true; let entry; try { this.cleanBlockQueue(); entry = this.peekBlockQueue(); const isForkedBlock = entry !== undefined && entry.block.index === this.currentBlockIndex && this.mutableCurrentBlock !== undefined && !client_core_1.common.uInt256Equal(entry.block.hash, this.mutableCurrentBlock.hash); // tslint:disable-next-line no-loop-statement while (this.mutableRunning && entry !== undefined && (entry.block.index === this.currentBlockIndex + 1 && (this.mutableCurrentBlock === undefined || client_core_1.common.uInt256Equal(entry.block.previousHash, this.mutableCurrentBlock.hash)) || isForkedBlock)) { entry = this.mutableBlockQueue.dequeue(); const entryNonNull = entry; if (isForkedBlock) { [this.mutableCurrentBlock, this.mutablePreviousBlock] = await Promise.all([this.block.tryGet({ hashOrIndex: entryNonNull.block.index - 1 }), this.block.tryGet({ hashOrIndex: entryNonNull.block.index - 2 })]); this.mutableCurrentHeader = this.mutableCurrentBlock === undefined ? undefined : this.mutableCurrentBlock.header; this.mutableWriteBlockchain = undefined; this.mutableWriteBlockchain = this.createWriteBlockchain(); } await entry.monitor.withData({ [utils_1.labels.NEO_BLOCK_INDEX]: entry.block.index }).captureSpanLog(async (span) => this.persistBlockInternal(span, entryNonNull.block, entryNonNull.unsafe), { name: 'neo_blockchain_persist_block_top_level', level: { log: 'verbose', span: 'info' }, metric: { total: NEO_BLOCKCHAIN_PERSIST_BLOCK_DURATION_SECONDS, error: NEO_BLOCKCHAIN_PERSIST_BLOCK_FAILURES_TOTAL }, trace: true }); entry.resolve(); this.mutableBlock$.next(entry.block); NEO_BLOCKCHAIN_BLOCK_INDEX_GAUGE.set(entry.block.index); NEO_BLOCKCHAIN_PERSIST_BLOCK_LATENCY_SECONDS.observe(this.monitor.nowSeconds() - entry.block.timestamp); this.cleanBlockQueue(); entry = this.peekBlockQueue(); } } catch (error) { if (entry !== undefined) { entry.reject(error); } } finally { this.mutablePersistingBlocks = false; if (this.mutableDoneRunningResolve !== undefined) { this.mutableDoneRunningResolve(); this.mutableDoneRunningResolve = undefined; } } } cleanBlockQueue() { let entry = this.peekBlockQueue(); // tslint:disable-next-line no-loop-statement while (entry !== undefined && entry.block.index <= this.currentBlockIndex) { if (entry.block.index !== this.currentBlockIndex || this.mutableCurrentBlock === undefined || client_core_1.common.uInt256Equal(entry.block.hash, this.mutableCurrentBlock.hash)) { this.mutableBlockQueue.dequeue(); entry.resolve(); entry = this.peekBlockQueue(); } } } peekBlockQueue() { if (this.mutableBlockQueue.length > 0) { return this.mutableBlockQueue.peek(); } return undefined; } start() { this.mutableBlock$ = new Subject_1.Subject(); this.mutablePersistingBlocks = false; this.mutableBlockQueue = new js_priority_queue_1.default({ comparator: (a, b) => a.block.index - b.block.index }); this.mutableInQueue = new Set(); this.mutableDoneRunningResolve = undefined; this.mutableRunning = true; this.monitor.log({ name: 'neo_blockchain_start' }); } // private readonly getVotes = async (transactions: ReadonlyArray<Transaction>): Promise<ReadonlyArray<Vote>> => { // const inputs = await Promise.all( // transactions.map(async (transaction) => // transaction.getReferences({ // getOutput: this.output.get, // }), // ), // ).then((results) => // results.reduce((acc, inputResults) => acc.concat(inputResults), []).map((output) => ({ // address: output.address, // asset: output.asset, // value: output.value.neg(), // })), // ); // const outputs = transactions // .reduce<ReadonlyArray<Output>>((acc, transaction) => acc.concat(transaction.outputs), []) // .map((output) => ({ // address: output.address, // asset: output.asset, // value: output.value, // })); // const changes = _.fromPairs( // Object.entries( // _.groupBy( // inputs // .concat(outputs) // .filter((output) => common.uInt256Equal(output.asset, this.settings.governingToken.hash)), // (output) => common.uInt160ToHex(output.address), // ), // ).map(([addressHex, addressOutputs]) => [ // addressHex, // addressOutputs.reduce((acc, output) => acc.add(output.value), utils.ZERO), // ]), // ); // const votes = await this.account.all$ // .pipe( // filter((account) => account.votes.length > 0), // map((account) => { // let balance = account.balances[this.settings.governingToken.hashHex]; // balance = balance === undefined ? utils.ZERO : balance; // const change = changes[account.hashHex]; // balance = balance.add(change === undefined ? utils.ZERO : change); // return balance.lte(utils.ZERO) // ? undefined // : { // publicKeys: account.votes, // count: balance, // }; // }), // toArray(), // ) // .toPromise(); // if (votes.length === 0) { // return [ // { // publicKeys: this.settings.standbyValidators, // count: this.settings.governingToken.asset.amount, // }, // ]; // } // return votes.filter(commonUtils.notNull); // }; async persistBlockInternal(monitor, block, unsafe) { NEO_BLOCKCHAIN_PERSISTING_BLOCK_INDEX_GAUGE.set(block.index); if (!unsafe) { await this.verifyBlock(block, monitor); } const blockchain = this.createWriteBlockchain(); await blockchain.persistBlock(monitor, block); const writeBlockchain = this.mutableWriteBlockchain; if (writeBlockchain === undefined) { throw new Error('Something went wrong'); } await monitor.captureSpan(async () => this.storage.commit(writeBlockchain.getChangeSet()), { name: 'neo_blockchain_persist_block_commit_storage' }); blockchain.setStorage(this.storage); this.mutableWriteBlockchain = blockchain; this.mutablePreviousBlock = this.mutableCurrentBlock; this.mutableCurrentBlock = block; this.mutableCurrentHeader = block.header; } createWriteBlockchain() { return new WriteBatchBlockchain_1.WriteBatchBlockchain({ settings: this.settings, currentBlock: this.mutableCurrentBlock, currentHeader: this.mutableCurrentHeader, storage: this.mutableWriteBlockchain === undefined ? this.storage : this.mutableWriteBlockchain, vm: this.vm, getValidators: this.getValidators }); } getMonitor(monitor) { if (monitor === undefined) { return this.monitor; } return monitor.at(NAMESPACE); } } exports.Blockchain = Blockchain; //# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkJsb2NrY2hhaW4udHMiXSwibmFtZXMiOlsiTkFNRVNQQUNFIiwiTkVPX0JMT0NLQ0hBSU5fUEVSU0lTVF9CTE9DS19EVVJBVElPTl9TRUNPTkRTIiwibWV0cmljcyIsImNyZWF0ZUhpc3RvZ3JhbSIsIm5hbWUiLCJORU9fQkxPQ0tDSEFJTl9QRVJTSVNUX0JMT0NLX0ZBSUxVUkVTX1RPVEFMIiwiY3JlYXRlQ291bnRlciIsIk5FT19CTE9DS0NIQUlOX0JMT0NLX0lOREVYX0dBVUdFIiwiY3JlYXRlR2F1Z2UiLCJoZWxwIiwiTkVPX0JMT0NLQ0hBSU5fUEVSU0lTVElOR19CTE9DS19JTkRFWF9HQVVHRSIsIk5FT19CTE9DS0NIQUlOX1BFUlNJU1RfQkxPQ0tfTEFURU5DWV9TRUNPTkRTIiwiYnVja2V0cyIsIkJsb2NrY2hhaW4iLCJvcHRpb25zIiwibXV0YWJsZVBlcnNpc3RpbmdCbG9ja3MiLCJtdXRhYmxlQmxvY2tRdWV1ZSIsIlByaW9yaXR5UXVldWUiLCJjb21wYXJhdG9yIiwiYSIsImIiLCJibG9jayIsImluZGV4IiwibXV0YWJsZUluUXVldWUiLCJTZXQiLCJtdXRhYmxlUnVubmluZyIsIm11dGFibGVCbG9jayQiLCJTdWJqZWN0IiwiZ2V0VmFsaWRhdG9ycyIsInRyYW5zYWN0aW9ucyIsIm1vbml0b3IiLCJnZXRNb25pdG9yIiwiY2FwdHVyZVNwYW5Mb2ciLCJsZXZlbCIsImxvZyIsInNwYW4iLCJjYWxjdWxhdGVDbGFpbUFtb3VudCIsImNsYWltcyIsInNwZW50Q29pbnMiLCJQcm9taXNlIiwiYWxsIiwibWFwIiwiY2xhaW0iLCJ0cnlHZXRTcGVudENvaW4iLCJmaWx0ZXJlZFNwZW50Q29pbnMiLCJmaWx0ZXIiLCJjb21tb25VdGlscyIsIm5vdE51bGwiLCJsZW5ndGgiLCJDb2luVW5zcGVudEVycm9yIiwic29tZSIsImNvaW4iLCJjbGFpbWVkIiwiQ29pbkNsYWltZWRFcnJvciIsImNvbW1vbiIsInVJbnQyNTZFcXVhbCIsIm91dHB1dCIsImFzc2V0Iiwic2V0dGluZ3MiLCJnb3Zlcm5pbmdUb2tlbiIsImhhc2giLCJJbnZhbGlkQ2xhaW1FcnJvciIsInV0aWxzIiwiY29pbnMiLCJ2YWx1ZSIsInN0YXJ0SGVpZ2h0IiwiZW5kSGVpZ2h0IiwiZGVjcmVtZW50SW50ZXJ2YWwiLCJnZW5lcmF0aW9uQW1vdW50IiwiZ2V0U3lzdGVtRmVlIiwiaGVhZGVyIiwiZ2V0IiwiaGFzaE9ySW5kZXgiLCJibG9ja0RhdGEiLCJzeXN0ZW1GZWUiLCJ2ZXJpZnlTY3JpcHQiLCJzY3JpcHRDb250YWluZXIiLCJ3aXRuZXNzIiwidmVyaWZpY2F0aW9uIiwiYnVpbGRlciIsIlNjcmlwdEJ1aWxkZXIiLCJlbWl0QXBwQ2FsbFZlcmlmaWNhdGlvbiIsImJ1aWxkIiwidUludDE2MEVxdWFsIiwiY3J5cHRvIiwidG9TY3JpcHRIYXNoIiwiV2l0bmVzc1ZlcmlmeUVycm9yIiwiYmxvY2tjaGFpbiIsImNyZWF0ZVdyaXRlQmxvY2tjaGFpbiIsInJlc3VsdCIsInZtIiwiZXhlY3V0ZVNjcmlwdHMiLCJzY3JpcHRzIiwiY29kZSIsImludm9jYXRpb24iLCJwdXNoT25seSIsInRyaWdnZXJUeXBlIiwiVHJpZ2dlclR5cGUiLCJWZXJpZmljYXRpb24iLCJhY3Rpb24iLCJOVUxMX0FDVElPTiIsImdhcyIsIlpFUk8iLCJzdGFjayIsIlNjcmlwdFZlcmlmeUVycm9yIiwidG9wIiwiYXNCb29sZWFuIiwidHJ5R2V0SW52b2NhdGlvbkRhdGEiLCJ0cmFuc2FjdGlvbiIsImRhdGEiLCJpbnZvY2F0aW9uRGF0YSIsInRyeUdldCIsInVuZGVmaW5lZCIsImNvbnRyYWN0cyIsImFjdGlvbnMiLCJhc3NldEhhc2giLCJyZXNvbHZlIiwiY29udHJhY3RIYXNoZXMiLCJjb250cmFjdEhhc2giLCJjb250cmFjdCIsImFjdGlvbkluZGV4U3RhcnQiLCJlcSIsImFjdGlvbkluZGV4U3RvcCIsImdldEFsbCQiLCJpbmRleFN0YXJ0IiwiaW5kZXhTdG9wIiwic3ViIiwiT05FIiwicGlwZSIsInRvQXJyYXkiLCJ0b1Byb21pc2UiLCJkZWxldGVkQ29udHJhY3RIYXNoZXMiLCJtaWdyYXRlZENvbnRyYWN0SGFzaGVzIiwidm90ZVVwZGF0ZXMiLCJ0cnlHZXRUcmFuc2FjdGlvbkRhdGEiLCJ0cmFuc2FjdGlvbkRhdGEiLCJnZXRVbmNsYWltZWQiLCJhY2NvdW50VW5jbGFpbWVkIiwidGhlbiIsInZhbHVlcyIsImlucHV0IiwiZ2V0VW5zcGVudCIsInVuc3BlbnQiLCJhY2NvdW50VW5zcGVudCIsImdldEFsbFZhbGlkYXRvcnMiLCJ2YWxpZGF0b3IiLCJhbGwkIiwiaXNTcGVudCIsImVuZEhlaWdodHMiLCJzdG9yYWdlIiwibXV0YWJsZUN1cnJlbnRCbG9jayIsImN1cnJlbnRCbG9jayIsIm11dGFibGVQcmV2aW91c0Jsb2NrIiwicHJldmlvdXNCbG9jayIsIm11dGFibGVDdXJyZW50SGVhZGVyIiwiY3VycmVudEhlYWRlciIsInNldHRpbmdzJCIsIkJlaGF2aW9yU3ViamVjdCIsImF0Iiwic2V0IiwiY3VycmVudEJsb2NrSW5kZXgiLCJzZWxmIiwiZGVzZXJpYWxpemVXaXJlQ29udGV4dCIsIm1lc3NhZ2VNYWdpYyIsImZlZUNvbnRleHQiLCJnZXRPdXRwdXQiLCJ1dGlsaXR5VG9rZW4iLCJmZWVzIiwicmVnaXN0ZXJWYWxpZGF0b3JGZWUiLCJzZXJpYWxpemVKU09OQ29udGV4dCIsImFkZHJlc3NWZXJzaW9uIiwibXV0YWJsZVdyaXRlQmxvY2tjaGFpbiIsInN0YXJ0IiwiY3JlYXRlIiwidHJ5R2V0TGF0ZXN0IiwicGVyc2lzdEhlYWRlcnMiLCJnZW5lc2lzQmxvY2siLCJwZXJzaXN0QmxvY2siLCJnZXRWYWx1ZSIsIkdlbmVzaXNCbG9ja05vdFJlZ2lzdGVyZWRFcnJvciIsImJsb2NrJCIsImlzUGVyc2lzdGluZ0Jsb2NrIiwiYWNjb3VudCIsInN0b3JhZ2VJdGVtIiwidmFsaWRhdG9yc0NvdW50Iiwic3RvcCIsImRvbmVSdW5uaW5nUHJvbWlzZSIsIm11dGFibGVEb25lUnVubmluZ1Jlc29sdmUiLCJ1cGRhdGVTZXR0aW5ncyIsIm5leHQiLCJ1bnNhZmUiLCJyZWplY3QiLCJoYXMiLCJoYXNoSGV4IiwiYWRkIiwicXVldWUiLCJwZXJzaXN0QmxvY2tzQXN5bmMiLCJfaGVhZGVycyIsInZlcmlmeUJsb2NrIiwid2l0aERhdGEiLCJsYWJlbHMiLCJORU9fQkxPQ0tfSU5ERVgiLCJjYXB0dXJlU3BhbiIsInZlcmlmeSIsInRyeUdldEJsb2NrIiwidHJ5R2V0SGVhZGVyIiwiZ2V0QXNzZXQiLCJ0cnlHZXRBY2NvdW50Iiwic3RhbmRieVZhbGlkYXRvcnMiLCJjdXJyZW50SGVpZ2h0IiwidmVyaWZ5Q29uc2Vuc3VzUGF5bG9hZCIsInBheWxvYWQiLCJORU9fQ09OU0VOU1VTX0hBU0giLCJjdXJyZW50SW5kZXgiLCJjdXJyZW50QmxvY2tIYXNoIiwidmVyaWZ5VHJhbnNhY3Rpb24iLCJtZW1Qb29sIiwiTkVPX1RSQU5TQUNUSU9OX0hBU0giLCJlcnJvciIsImluY2x1ZGVzIiwiVW5rbm93blZlcmlmeUVycm9yIiwibWVzc2FnZSIsImludm9rZVNjcmlwdCIsInNjcmlwdCIsIkludm9jYXRpb25UcmFuc2FjdGlvbiIsIk9ORV9IVU5EUkVEX0ZJWEVEOCIsImludm9rZVRyYW5zYWN0aW9uIiwid3JhcEV4ZWN1dGVTY3JpcHRzIiwidHlwZSIsIlNjcmlwdENvbnRhaW5lclR5cGUiLCJUcmFuc2FjdGlvbiIsIkFwcGxpY2F0aW9uIiwic2tpcFdpdG5lc3NWZXJpZnkiLCJyZXNldCIsImVudHJ5IiwiY2xlYW5CbG9ja1F1ZXVlIiwicGVla0Jsb2NrUXVldWUiLCJpc0ZvcmtlZEJsb2NrIiwicHJldmlvdXNIYXNoIiwiZGVxdWV1ZSIsImVudHJ5Tm9uTnVsbCIsInBlcnNpc3RCbG9ja0ludGVybmFsIiwibWV0cmljIiwidG90YWwiLCJ0cmFjZSIsIm9ic2VydmUiLCJub3dTZWNvbmRzIiwidGltZXN0YW1wIiwicGVlayIsIndyaXRlQmxvY2tjaGFpbiIsIkVycm9yIiwiY29tbWl0IiwiZ2V0Q2hhbmdlU2V0Iiwic2V0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