@neo-one/node-blockchain-esnext-cjs
Version:
NEO•ONE NEO blockchain implementation.
678 lines (677 loc) • 110 kB
JavaScript
"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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