Merge PR #928 from 'nodech/wallet-coinselection'
This commit is contained in:
commit
f0a81dac67
33 changed files with 7791 additions and 726 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -3,7 +3,7 @@
|
|||
## Unreleased
|
||||
|
||||
**When upgrading to this version of hsd, you must pass `--chain-migrate=4`
|
||||
and `--wallet-migrate=6` when you run it for the first time.**
|
||||
and `--wallet-migrate=7` when you run it for the first time.**
|
||||
|
||||
### Wallet Changes
|
||||
|
||||
|
|
@ -14,6 +14,14 @@ and `--wallet-migrate=6` when you run it for the first time.**
|
|||
the namestate when the wallet owns the name.
|
||||
- Introduce admin `POST /recalculate-balances`, useful if the post-migration
|
||||
recalculation was not triggered and wallet balances are not correct.
|
||||
- The TX creation HTTP Endpoints now supports new values for the `selection`
|
||||
property. These new strategies use database iterators instead of loading all
|
||||
coins into RAM.
|
||||
- `db-value` - This is a database alternative to `value` and new default.
|
||||
- `db-age` - A database alternative `age`.
|
||||
- `db-all` - A database alternative `all`.
|
||||
- `db-sweepdust` - Select smallest coins first.
|
||||
- Add `sweepdustMinValue` option for TX creation endpoints, default 1.
|
||||
|
||||
#### Wallet/WalletDB API
|
||||
- `Wallet.zap` now returns the number of transactions zapped instead of their hashes.
|
||||
|
|
|
|||
738
bench/wallet-coinselector.js
Normal file
738
bench/wallet-coinselector.js
Normal file
|
|
@ -0,0 +1,738 @@
|
|||
/*!
|
||||
* bench/wallet-coinselector.js - benchmark wallet coin selections.
|
||||
*
|
||||
* This can prepare coin set for the wallet and then run different
|
||||
* coin selection algorithms on it. The wallet will run on the regtest.
|
||||
*
|
||||
* Usage:
|
||||
* node bench/wallet-coinselector.js [--prefix=path] [--unspendable=<number>]
|
||||
* [--spendable=<number>] [--opens=<number>]
|
||||
* [--per-block=<number>] [--cleanup]
|
||||
* [--ops-per-type=<number>] [--skip-init]
|
||||
* [--output=<file>] [--no-print] [--no-logs]
|
||||
* [--skip-sends] [--skip-bids]
|
||||
* [--skip-updates] [--skip-renewals]
|
||||
* [--skip-transfers]
|
||||
*
|
||||
* Options:
|
||||
* - `prefix` The location to store the walletdb. If data exists,
|
||||
* it will be used for the benchmark. (Default: tmp)
|
||||
* - `opens` The number of 0 value OPEN coins.
|
||||
* Default: 1 000.
|
||||
* - `spendable` The number of SPENDABLE coins.
|
||||
* Default: 2 000.
|
||||
* - `unspendable` The number of UNSPENDABLE coins.
|
||||
* Default: 1 500.
|
||||
* - `per-block` The number of each coin type per block.
|
||||
* Default: 300.
|
||||
* - `cleanup` Remove the walletdb after the benchmark.
|
||||
* Default: false.
|
||||
* - `ops-per-type` The number of operations per type.
|
||||
* Default: 200.
|
||||
* - `max-pending` The maximum number of coins to be spent. Ops will zap
|
||||
* all pending txs after every `max-pending` operations.
|
||||
* Default: 50.
|
||||
* - `skip-init` Skip the initialization of the wallet. This will
|
||||
* only run the benchmarks on the existing data.
|
||||
* Default: false.
|
||||
* - `output` The output file to store the benchmark results.
|
||||
* Default: null.
|
||||
* - `no-print` Do not print the benchmark results to the console.
|
||||
* Default: false.
|
||||
* - `no-logs` Do not print the logs to the console.
|
||||
* Default: false.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
process.title = 'hsd-coinselector-bench';
|
||||
|
||||
const Config = require('bcfg');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const bfs = require('bfile');
|
||||
const Covenant = require('../lib/primitives/covenant');
|
||||
const Network = require('../lib/protocol/network');
|
||||
const WalletDB = require('../lib/wallet/walletdb');
|
||||
const NameState = require('../lib/covenants/namestate');
|
||||
const {Resource} = require('../lib/dns/resource');
|
||||
const wcommon = require('../lib/wallet/common');
|
||||
const wutils = require('../test/util/wallet');
|
||||
const random = require('bcrypto/lib/random');
|
||||
const primutils = require('../test/util/primitives');
|
||||
const {DB_VALUE, DB_AGE} = wcommon.coinSelectionTypes;
|
||||
|
||||
/** @typedef {import('../lib/covenants/rules').types} covenantTypes */
|
||||
/** @typedef {import('../lib/wallet/wallet')} Wallet */
|
||||
|
||||
(async () => {
|
||||
const cfg = new Config('hsd');
|
||||
|
||||
cfg.load({
|
||||
argv: true,
|
||||
env: true
|
||||
});
|
||||
|
||||
const network = Network.get('regtest');
|
||||
const tmp = path.join(os.tmpdir(), 'hsd-bench');
|
||||
const prefix = cfg.str('prefix', tmp);
|
||||
const options = {
|
||||
opens: cfg.int('opens', 1_000),
|
||||
spendable: cfg.int('spendable', 2_000),
|
||||
unspendable: cfg.int('unspendable', 1_500),
|
||||
perBlock: cfg.int('per-block', 400),
|
||||
cleanup: cfg.bool('cleanup', false),
|
||||
opsPerType: cfg.int('ops-per-type', 500),
|
||||
maxPending: cfg.int('max-pending', 200),
|
||||
skipInit: cfg.bool('skip-init', false),
|
||||
noPrint: cfg.bool('no-print', false),
|
||||
output: cfg.str('output', null),
|
||||
noLogs: cfg.bool('no-logs', false),
|
||||
|
||||
skipSends: cfg.bool('skip-sends', false),
|
||||
skipBids: cfg.bool('skip-bids', false),
|
||||
skipUpdates: cfg.bool('skip-updates', false),
|
||||
skipRenewals: cfg.bool('skip-renewals', false),
|
||||
skipTransfers: cfg.bool('skip-transfers', false)
|
||||
};
|
||||
|
||||
if (options.maxPending > options.opsPerType)
|
||||
throw new Error('max-pending cannot be greater than ops-per-type.');
|
||||
|
||||
options.opens = Math.max(options.opens, options.maxPending);
|
||||
options.unspendable = Math.max(options.unspendable, options.maxPending);
|
||||
|
||||
if (!await bfs.exists(prefix))
|
||||
await bfs.mkdirp(prefix);
|
||||
|
||||
let consoleLog = console.log.bind(console);
|
||||
let stdoutWrite = process.stdout.write.bind(process.stdout);
|
||||
|
||||
if (options.noLogs) {
|
||||
consoleLog = () => {};
|
||||
stdoutWrite = () => {};
|
||||
}
|
||||
|
||||
consoleLog(`WalletDB location: ${prefix}`);
|
||||
|
||||
const wdb = new WalletDB({
|
||||
memory: false,
|
||||
network,
|
||||
prefix
|
||||
});
|
||||
|
||||
await wdb.open();
|
||||
await wdb.primary.zap(-1, 0);
|
||||
|
||||
if (!options.skipInit) {
|
||||
const left = {
|
||||
opens: options.opens,
|
||||
spendable: options.spendable,
|
||||
unspendable: options.unspendable
|
||||
};
|
||||
|
||||
consoleLog('Collect existing data.');
|
||||
const coins = await wdb.primary.getCoins(0);
|
||||
|
||||
for (const coin of coins) {
|
||||
if (coin.covenant.type === Covenant.types.OPEN) {
|
||||
left.opens--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coin.covenant.type === Covenant.types.NONE
|
||||
|| coin.covenant.type === Covenant.types.REDEEM) {
|
||||
left.spendable--;
|
||||
continue;
|
||||
}
|
||||
|
||||
left.unspendable--;
|
||||
}
|
||||
|
||||
consoleLog(`Coins: ${coins.length}, Left to mine:
|
||||
opens: ${left.opens}
|
||||
spendable: ${left.spendable}
|
||||
unspendable: ${left.unspendable}`);
|
||||
|
||||
const opens = distributeCoinsPerBlock(left.opens, options.perBlock);
|
||||
const spendable = distributeCoinsPerBlock(left.spendable,
|
||||
options.perBlock);
|
||||
const unspendable = distributeCoinsPerBlock(left.unspendable,
|
||||
options.perBlock);
|
||||
|
||||
const max = Math.max(opens.length, spendable.length, unspendable.length);
|
||||
consoleLog(`Blocks to mine: ${max}`);
|
||||
|
||||
for (let i = 0; i < max; i++) {
|
||||
const openTXs = await createOpenTXs(wdb.primary, opens[i] || 0);
|
||||
const spendTXs = await createSpendTXs(wdb.primary, spendable[i] || 0);
|
||||
const unspendTXs = await createUnspendableTXs(wdb.primary,
|
||||
unspendable[i] || 0);
|
||||
|
||||
consoleLog(`Block: ${wdb.height + 1}, `
|
||||
+ `opens: ${openTXs.length}, `
|
||||
+ `spends: ${spendTXs.length}, `
|
||||
+ `unspendables: ${unspendTXs.length}`);
|
||||
|
||||
await wdb.addBlock(wutils.nextBlock(wdb),
|
||||
[].concat(openTXs, spendTXs, unspendTXs));
|
||||
}
|
||||
|
||||
const treeInterval = network.names.treeInterval;
|
||||
const biddingPeriod = network.names.biddingPeriod;
|
||||
const revealPeriod = network.names.revealPeriod;
|
||||
|
||||
if (max) {
|
||||
consoleLog('Progressing to the closed phase...');
|
||||
for (let i = 0; i < biddingPeriod + revealPeriod; i++) {
|
||||
await wdb.addBlock(wutils.nextBlock(wdb), []);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare bidding names
|
||||
const existingBiddingNames = await getBiddableNames(wdb.primary);
|
||||
|
||||
consoleLog(`Existing bidding names: ${existingBiddingNames.length}`);
|
||||
if (existingBiddingNames.length < options.maxPending) {
|
||||
stdoutWrite('Creating bidding names...');
|
||||
const biddingNames = Array.from({ length: options.maxPending }, () => {
|
||||
return primutils.randomName(30);
|
||||
});
|
||||
|
||||
const openInfos = biddingNames.map((name) => {
|
||||
return {
|
||||
value: 0,
|
||||
covenant: {
|
||||
type: Covenant.types.OPEN,
|
||||
name
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const txs = await wutils.createInboundTXs(wdb.primary, openInfos, {
|
||||
txPerOutput: true,
|
||||
createAddress: true
|
||||
});
|
||||
|
||||
await wdb.addBlock(wutils.nextBlock(wdb), txs);
|
||||
|
||||
for (let i = 0; i < treeInterval + 1; i++) {
|
||||
// progress to the bidding phase.
|
||||
await wdb.addBlock(wutils.nextBlock(wdb), []);
|
||||
}
|
||||
|
||||
stdoutWrite(' Done.\n');
|
||||
}
|
||||
|
||||
await wdb.primary.zap(-1, 0);
|
||||
consoleLog('Wallet initialized.');
|
||||
}
|
||||
|
||||
const wallet = wdb.primary;
|
||||
|
||||
const benchmarks = new BenchmarkResults({
|
||||
opens: options.opens,
|
||||
spendable: options.spendable,
|
||||
unspendable: options.unspendable,
|
||||
maxPending: options.maxPending
|
||||
});
|
||||
|
||||
const runOperations = async (sendTXFn) => {
|
||||
await wallet.zap(-1, 0);
|
||||
|
||||
let pending = 0;
|
||||
for (let i = 0; i < options.opsPerType; i++) {
|
||||
await sendTXFn(pending);
|
||||
pending++;
|
||||
|
||||
if (i % options.maxPending === 0) {
|
||||
await wallet.zap(-1, 0);
|
||||
pending = 0;
|
||||
}
|
||||
}
|
||||
|
||||
await wallet.zap(-1, 0);
|
||||
};
|
||||
|
||||
// Benchmark normal sends.
|
||||
consoleLog(`Running benchmarks...
|
||||
${options.opsPerType} operations per type.
|
||||
${options.maxPending} max pending.`);
|
||||
|
||||
const selections = [
|
||||
'random',
|
||||
'value',
|
||||
DB_VALUE,
|
||||
'age',
|
||||
DB_AGE
|
||||
];
|
||||
|
||||
for (const selection of selections) {
|
||||
if (options.skipSends)
|
||||
continue;
|
||||
|
||||
stdoutWrite(`Sending ${selection} selection...`);
|
||||
await runOperations(async (pending) => {
|
||||
const min = Math.min(options.spendable * 1e5 / options.maxPending,
|
||||
1e6);
|
||||
const max = Math.min(options.spendable * 1e5 / options.maxPending,
|
||||
1000e6);
|
||||
const value = random.randomRange(min, max);
|
||||
const address = primutils.randomP2PKAddress();
|
||||
const before = process.hrtime.bigint();
|
||||
await wallet.send({
|
||||
selection,
|
||||
outputs: [{
|
||||
value,
|
||||
address
|
||||
}]
|
||||
});
|
||||
|
||||
const after = process.hrtime.bigint();
|
||||
|
||||
const entry = new BenchmarkEntry('send', selection,
|
||||
after - before,pending);
|
||||
|
||||
benchmarks.addResult(entry);
|
||||
});
|
||||
|
||||
stdoutWrite(' Done.\n');
|
||||
}
|
||||
|
||||
for (const selection of selections) {
|
||||
if (options.skipBids)
|
||||
continue;
|
||||
stdoutWrite(`Bidding ${selection} selection...`);
|
||||
|
||||
const biddingNames = await getBiddableNames(wallet);
|
||||
|
||||
if (biddingNames.length < options.maxPending)
|
||||
throw new Error('Not enough bidding names to benchmark.');
|
||||
|
||||
await runOperations(async (pending) => {
|
||||
const min = Math.min(options.spendable * 1e5 / options.maxPending,
|
||||
1e6);
|
||||
const max = Math.min(options.spendable * 1e5 / options.maxPending,
|
||||
1000e6);
|
||||
const value = random.randomRange(min, max);
|
||||
const name = biddingNames[pending];
|
||||
const before = process.hrtime.bigint();
|
||||
await wallet.sendBid(name, value, value, {
|
||||
selection
|
||||
});
|
||||
|
||||
const after = process.hrtime.bigint();
|
||||
|
||||
const entry = new BenchmarkEntry('bid', selection,
|
||||
after - before, pending);
|
||||
|
||||
benchmarks.addResult(entry);
|
||||
});
|
||||
|
||||
stdoutWrite(' Done.\n');
|
||||
}
|
||||
|
||||
const namestates = await wallet.getNames();
|
||||
const selectedOwned = [];
|
||||
|
||||
for (const ns of namestates) {
|
||||
const {hash, index} = ns.owner;
|
||||
const coin = await wallet.getCoin(hash, index);
|
||||
|
||||
if (!coin)
|
||||
continue;
|
||||
|
||||
if (ns.state(wdb.height, network) === NameState.states.CLOSED) {
|
||||
if (ns.isExpired(wdb.height, network))
|
||||
continue;
|
||||
|
||||
selectedOwned.push(ns.name.toString('ascii'));
|
||||
}
|
||||
|
||||
if (selectedOwned.length >= options.maxPending)
|
||||
break;
|
||||
}
|
||||
|
||||
if (selectedOwned.length < options.maxPending)
|
||||
throw new Error('Not enough owned names to benchmark.');
|
||||
|
||||
const res = Resource.fromString('Resource');
|
||||
for (const selection of selections) {
|
||||
if (options.skipUpdates)
|
||||
continue;
|
||||
stdoutWrite(`Updating ${selection} selection...`);
|
||||
|
||||
await runOperations(async (pending) => {
|
||||
const before = process.hrtime.bigint();
|
||||
await wallet.sendUpdate(selectedOwned[pending], res, { selection });
|
||||
const after = process.hrtime.bigint();
|
||||
|
||||
const entry = new BenchmarkEntry('update', selection,
|
||||
after - before, pending);
|
||||
benchmarks.addResult(entry);
|
||||
});
|
||||
|
||||
stdoutWrite(' Done.\n');
|
||||
}
|
||||
|
||||
for (const selection of selections) {
|
||||
if (options.skipRenewals)
|
||||
continue;
|
||||
stdoutWrite(`Renewing ${selection} selection...`);
|
||||
|
||||
await runOperations(async (pending) => {
|
||||
const before = process.hrtime.bigint();
|
||||
await wallet.sendRenewal(selectedOwned[pending], { selection });
|
||||
const after = process.hrtime.bigint();
|
||||
|
||||
const entry = new BenchmarkEntry('renew', selection,
|
||||
after - before, pending);
|
||||
benchmarks.addResult(entry);
|
||||
});
|
||||
|
||||
stdoutWrite(' Done.\n');
|
||||
}
|
||||
|
||||
// do transfer at the end
|
||||
for (const selection of selections) {
|
||||
if (options.skipTransfers)
|
||||
continue;
|
||||
|
||||
stdoutWrite(`Transfering ${selection} selection...`);
|
||||
|
||||
const addr = primutils.randomP2PKAddress();
|
||||
await runOperations(async (pending) => {
|
||||
const before = process.hrtime.bigint();
|
||||
await wallet.sendTransfer(selectedOwned[pending], addr, { selection });
|
||||
const after = process.hrtime.bigint();
|
||||
|
||||
const entry = new BenchmarkEntry('transfer', selection,
|
||||
after - before, pending);
|
||||
benchmarks.addResult(entry);
|
||||
});
|
||||
|
||||
stdoutWrite(' Done.\n');
|
||||
}
|
||||
|
||||
benchmarks.calculateStats();
|
||||
|
||||
if (!options.noPrint)
|
||||
benchmarks.print();
|
||||
|
||||
if (options.output) {
|
||||
const json = benchmarks.toJSON();
|
||||
await bfs.writeFile(options.output, JSON.stringify(json, null, 2));
|
||||
}
|
||||
|
||||
await wdb.close();
|
||||
|
||||
if (options.cleanup)
|
||||
await bfs.rimraf(prefix);
|
||||
})().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
class BenchmarkEntry {
|
||||
/**
|
||||
* @param {String} type
|
||||
* @param {String} selection
|
||||
* @param {BigInt} elapsed
|
||||
* @param {Number} pending
|
||||
*/
|
||||
constructor(type, selection, elapsed, pending) {
|
||||
/** @type {String} */
|
||||
this.type = type;
|
||||
/** @type {String} */
|
||||
this.selection = selection;
|
||||
/** @type {BigInt} */
|
||||
this.elapsed = elapsed;
|
||||
/** @type {Number} */
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
get key() {
|
||||
return `${this.type}-${this.selection}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} BenchmarkResults
|
||||
* @property {String} type
|
||||
* @property {String} selection
|
||||
* @property {Number} opens
|
||||
* @property {Number} spendable
|
||||
* @property {Number} unspendable
|
||||
* @property {Number} maxPending
|
||||
* @property {Number} ops
|
||||
* @property {BigInt} min
|
||||
* @property {BigInt} max
|
||||
* @property {BigInt} median
|
||||
* @property {BigInt} percentile95
|
||||
* @property {BigInt} avg
|
||||
*/
|
||||
|
||||
class BenchmarkResults {
|
||||
constructor(options = {}) {
|
||||
this.opens = options.opens || 0;
|
||||
this.spendable = options.spendable || 0;
|
||||
this.unspendable = options.unspendable || 0;
|
||||
this.maxPending = options.maxPending || 0;
|
||||
/** @type Map<String, BenchmarkEntry[]> */
|
||||
this.benchmarksPerType = new Map();
|
||||
|
||||
/** @type Map<String, BenchmarkResults> */
|
||||
this.results = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BenchmarkEntry} entry
|
||||
*/
|
||||
|
||||
addResult(entry) {
|
||||
const key = entry.key;
|
||||
|
||||
if (!this.benchmarksPerType.has(key))
|
||||
this.benchmarksPerType.set(key, []);
|
||||
|
||||
const entries = this.benchmarksPerType.get(key);
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
calculateStats() {
|
||||
for (const [key, entries] of this.benchmarksPerType.entries()) {
|
||||
const result = {
|
||||
type: entries[0].type,
|
||||
selection: entries[0].selection,
|
||||
opens: this.opens,
|
||||
spendable: this.spendable,
|
||||
unspendable: this.unspendable,
|
||||
maxPending: this.maxPending,
|
||||
ops: entries.length,
|
||||
min: BigInt(Number.MAX_SAFE_INTEGER),
|
||||
max: 0n,
|
||||
median: 0n,
|
||||
percentile95: 0n,
|
||||
avg: 0n
|
||||
};
|
||||
|
||||
const sorted = entries.sort((a, b) => Number(a.elapsed - b.elapsed));
|
||||
const p95 = Math.floor(sorted.length * 0.95);
|
||||
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
if (i === p95)
|
||||
result.percentile95 = sorted[i].elapsed;
|
||||
|
||||
if (sorted[i].elapsed < result.min)
|
||||
result.min = sorted[i].elapsed;
|
||||
|
||||
if (sorted[i].elapsed > result.max)
|
||||
result.max = sorted[i].elapsed;
|
||||
|
||||
result.avg += sorted[i].elapsed;
|
||||
}
|
||||
|
||||
if (sorted.length > 1 && sorted.length % 2 === 0) {
|
||||
const mid1 = sorted[sorted.length / 2 - 1].elapsed;
|
||||
const mid2 = sorted[sorted.length / 2].elapsed;
|
||||
result.median = (mid1 + mid2) / 2n;
|
||||
} else if (sorted.length > 0) {
|
||||
result.median = sorted[Math.floor(sorted.length / 2)].elapsed;
|
||||
}
|
||||
|
||||
result.avg /= BigInt(sorted.length);
|
||||
|
||||
this.results.set(key, result);
|
||||
}
|
||||
}
|
||||
|
||||
toResultsArray() {
|
||||
const resultTable = [];
|
||||
|
||||
for (const entry of this.results.values()) {
|
||||
resultTable.push({
|
||||
type: entry.type,
|
||||
selection: entry.selection,
|
||||
opens: entry.opens,
|
||||
spendable: entry.spendable,
|
||||
unspendable: entry.unspendable,
|
||||
maxPending: entry.maxPending,
|
||||
ops: entry.ops,
|
||||
minMs: formatElapsedTime(entry.min),
|
||||
maxMs: formatElapsedTime(entry.max),
|
||||
medianMs: formatElapsedTime(entry.median),
|
||||
percentile95ms: formatElapsedTime(entry.percentile95),
|
||||
avgMs: formatElapsedTime(entry.avg)
|
||||
});
|
||||
}
|
||||
|
||||
return resultTable;
|
||||
}
|
||||
|
||||
print() {
|
||||
if (this.results.size === 0)
|
||||
throw new Error('No results to print.');
|
||||
|
||||
console.table(this.toResultsArray());
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
if (this.results.size === 0)
|
||||
throw new Error('No results to print.');
|
||||
|
||||
return {
|
||||
data: this.toResultsArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function distributeCoinsPerBlock(left, perBlock) {
|
||||
if (left <= 0)
|
||||
return [];
|
||||
|
||||
const full = Math.floor(left / perBlock);
|
||||
const rest = left % perBlock;
|
||||
const coins = new Array(full).fill(perBlock);
|
||||
|
||||
if (rest > 0)
|
||||
coins.push(rest);
|
||||
|
||||
return coins;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Wallet} wallet
|
||||
* @param {Number} opens
|
||||
* @returns {Promise<TX[]>}
|
||||
*/
|
||||
|
||||
async function createOpenTXs(wallet, opens) {
|
||||
/** @type {wutils.OutputInfo[]} */
|
||||
const infos = [];
|
||||
|
||||
for (let i = 0; i < opens; i++) {
|
||||
const info = {
|
||||
// OPENs are mostly 0 values. It does not need to be this way, but it is.
|
||||
value: 0,
|
||||
covenant: { type: Covenant.types.OPEN }
|
||||
};
|
||||
|
||||
infos.push(info);
|
||||
}
|
||||
|
||||
const txs = await wutils.createInboundTXs(wallet, infos, {
|
||||
txPerOutput: true,
|
||||
createAddress: true
|
||||
});
|
||||
|
||||
return txs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Wallet} wallet
|
||||
* @param {Number} spendable
|
||||
* @param {Object} options
|
||||
* @param {Number} options.minValue
|
||||
* @param {Number} options.maxValue
|
||||
* @returns {Promise<TX[]>}
|
||||
*/
|
||||
|
||||
async function createSpendTXs(wallet, spendable, options = {}) {
|
||||
/** @type {wutils.OutputInfo[]} */
|
||||
const infos = [];
|
||||
const spendables = [
|
||||
Covenant.types.NONE,
|
||||
Covenant.types.REDEEM
|
||||
];
|
||||
|
||||
const {
|
||||
minValue = 1e5,
|
||||
maxValue = 100e6
|
||||
} = options;
|
||||
|
||||
for (let i = 0; i < spendable; i++) {
|
||||
const covenant = { type: spendables[i % spendables.length] };
|
||||
const value = random.randomRange(minValue, maxValue);
|
||||
const info = { value, covenant };
|
||||
|
||||
infos.push(info);
|
||||
}
|
||||
|
||||
const txs = await wutils.createInboundTXs(wallet, infos, {
|
||||
txPerOutput: true,
|
||||
createAddress: true
|
||||
});
|
||||
|
||||
return txs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Wallet} wallet
|
||||
* @param {Number} unspendable
|
||||
* @param {Object} options
|
||||
* @param {Number} options.minValue
|
||||
* @param {Number} options.maxValue
|
||||
* @returns {Promise<TX[]>}
|
||||
*/
|
||||
|
||||
async function createUnspendableTXs(wallet, unspendable, options = {}) {
|
||||
/** @type {wutils.OutputInfo[]} */
|
||||
const infos = [];
|
||||
const unspendables = [
|
||||
// Covenant.types.REGISTER,
|
||||
// Covenant.types.UPDATE,
|
||||
// Covenant.types.RENEW,
|
||||
Covenant.types.FINALIZE
|
||||
];
|
||||
|
||||
const {
|
||||
minValue = 1e5,
|
||||
maxValue = 100e6
|
||||
} = options;
|
||||
|
||||
for (let i = 0; i < unspendable; i++) {
|
||||
const covenant = { type: unspendables[i % unspendables.length] };
|
||||
const value = random.randomRange(minValue, maxValue);
|
||||
const info = { value, covenant };
|
||||
|
||||
infos.push(info);
|
||||
}
|
||||
|
||||
const txs = await wutils.createInboundTXs(wallet, infos, {
|
||||
txPerOutput: true,
|
||||
createAddress: true
|
||||
});
|
||||
|
||||
return txs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BigInt} elapsedNanos
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
function formatElapsedTime(elapsedNanos) {
|
||||
const nsInMs = 1000000n;
|
||||
|
||||
return Number(elapsedNanos) / Number(nsInMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Wallet} wallet
|
||||
* @returns {Promise<String[]>}
|
||||
*/
|
||||
|
||||
async function getBiddableNames(wallet) {
|
||||
const height = wallet.wdb.height;
|
||||
const network = wallet.network;
|
||||
const names = await wallet.getNames();
|
||||
const biddable = [];
|
||||
|
||||
for (const ns of names) {
|
||||
if (ns.state(height, network) === NameState.states.BIDDING) {
|
||||
biddable.push(ns.name.toString('ascii'));
|
||||
}
|
||||
}
|
||||
|
||||
return biddable;
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ const LevelBlockStore = require('./level');
|
|||
* @module blockstore
|
||||
*/
|
||||
|
||||
exports.create = (options) => {
|
||||
exports.create = function create(options) {
|
||||
const location = join(options.prefix, 'blocks');
|
||||
|
||||
return new LevelBlockStore({
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const FileBlockStore = require('./file');
|
|||
* @module blockstore
|
||||
*/
|
||||
|
||||
exports.create = (options) => {
|
||||
exports.create = function create(options) {
|
||||
if (options.memory) {
|
||||
return new LevelBlockStore({
|
||||
network: options.network,
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ class CoinView extends View {
|
|||
|
||||
/**
|
||||
* Spend an output.
|
||||
* @param {Outpoint} prevout
|
||||
* @param {Outpoint|Coin} prevout
|
||||
* @returns {CoinEntry|null}
|
||||
*/
|
||||
|
||||
|
|
@ -216,7 +216,7 @@ class CoinView extends View {
|
|||
|
||||
/**
|
||||
* Remove an output.
|
||||
* @param {Outpoint} prevout
|
||||
* @param {Outpoint|Coin} prevout
|
||||
* @returns {CoinEntry|null}
|
||||
*/
|
||||
|
||||
|
|
@ -232,7 +232,7 @@ class CoinView extends View {
|
|||
|
||||
/**
|
||||
* Test whether the view has an entry by prevout.
|
||||
* @param {Outpoint} prevout
|
||||
* @param {Outpoint|Coin} prevout
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
|
|
@ -248,7 +248,7 @@ class CoinView extends View {
|
|||
|
||||
/**
|
||||
* Get a single entry by prevout.
|
||||
* @param {Outpoint} prevout
|
||||
* @param {Outpoint|Coin} prevout
|
||||
* @returns {CoinEntry|null}
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -434,6 +434,14 @@ class MigrationContext {
|
|||
async saveState() {
|
||||
await this.migrator.saveState(this.state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Batch} b
|
||||
*/
|
||||
|
||||
writeState(b) {
|
||||
this.migrator.writeState(b, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
exports.Migrator = Migrator;
|
||||
|
|
|
|||
|
|
@ -383,17 +383,17 @@ class Covenant extends bio.Struct {
|
|||
/**
|
||||
* Set covenant to BID.
|
||||
* @param {Hash} nameHash
|
||||
* @param {Number} start
|
||||
* @param {Number} height
|
||||
* @param {Buffer} rawName
|
||||
* @param {Hash} blind
|
||||
* @returns {Covenant}
|
||||
*/
|
||||
|
||||
setBid(nameHash, start, rawName, blind) {
|
||||
setBid(nameHash, height, rawName, blind) {
|
||||
this.type = types.BID;
|
||||
this.items = [];
|
||||
this.pushHash(nameHash);
|
||||
this.pushU32(start);
|
||||
this.pushU32(height);
|
||||
this.push(rawName);
|
||||
this.pushHash(blind);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
const assert = require('bsert');
|
||||
const {encoding} = require('bufio');
|
||||
const {BufferMap} = require('buffer-map');
|
||||
const Script = require('../script/script');
|
||||
const TX = require('./tx');
|
||||
const Input = require('./input');
|
||||
|
|
@ -18,15 +17,18 @@ const Outpoint = require('./outpoint');
|
|||
const CoinView = require('../coins/coinview');
|
||||
const Path = require('../wallet/path');
|
||||
const WalletCoinView = require('../wallet/walletcoinview');
|
||||
const Address = require('./address');
|
||||
const consensus = require('../protocol/consensus');
|
||||
const policy = require('../protocol/policy');
|
||||
const Amount = require('../ui/amount');
|
||||
const Stack = require('../script/stack');
|
||||
const rules = require('../covenants/rules');
|
||||
const util = require('../utils/util');
|
||||
const {types} = rules;
|
||||
|
||||
const {
|
||||
CoinSelector,
|
||||
InMemoryCoinSource
|
||||
} = require('../utils/coinselector');
|
||||
|
||||
/** @typedef {import('../types').SighashType} SighashType */
|
||||
/** @typedef {import('../types').Hash} Hash */
|
||||
/** @typedef {import('../types').Amount} AmountValue */
|
||||
|
|
@ -34,6 +36,8 @@ const {types} = rules;
|
|||
/** @typedef {import('../protocol/network')} Network */
|
||||
/** @typedef {import('../workers/workerpool')} WorkerPool */
|
||||
/** @typedef {import('./keyring')} KeyRing */
|
||||
/** @typedef {import('./address')} Address */
|
||||
/** @typedef {import('../utils/coinselector')} coinselector */
|
||||
|
||||
/**
|
||||
* MTX
|
||||
|
|
@ -227,6 +231,18 @@ class MTX extends TX {
|
|||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the change output.
|
||||
* @returns {AmountValue} value - Returns -1 if no change output.
|
||||
*/
|
||||
|
||||
getChangeValue() {
|
||||
if (this.changeIndex === -1)
|
||||
return -1;
|
||||
|
||||
return this.outputs[this.changeIndex].value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify all transaction inputs.
|
||||
* @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS]
|
||||
|
|
@ -1018,14 +1034,26 @@ class MTX extends TX {
|
|||
/**
|
||||
* Select necessary coins based on total output value.
|
||||
* @param {Coin[]} coins
|
||||
* @param {Object?} options
|
||||
* @param {Object} options
|
||||
* @returns {Promise<CoinSelector>}
|
||||
* @throws on not enough funds available.
|
||||
*/
|
||||
|
||||
selectCoins(coins, options) {
|
||||
const selector = new CoinSelector(this, options);
|
||||
return selector.select(coins);
|
||||
async selectCoins(coins, options) {
|
||||
const source = new InMemoryCoinSource({
|
||||
coins,
|
||||
selection: options.selection
|
||||
});
|
||||
|
||||
await source.init();
|
||||
|
||||
if (options.selection === 'all')
|
||||
options.selectAll = true;
|
||||
|
||||
const selector = new CoinSelector(this, source, options);
|
||||
await selector.select();
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1112,7 +1140,9 @@ class MTX extends TX {
|
|||
/**
|
||||
* Select coins and fill the inputs.
|
||||
* @param {Coin[]} coins
|
||||
* @param {Object} options - See {@link MTX#selectCoins} options.
|
||||
* @param {Object} options - See
|
||||
* {@link CoinSelectorOptions} and
|
||||
* {@link CoinSourceOptions} options.
|
||||
* @returns {Promise<CoinSelector>}
|
||||
*/
|
||||
|
||||
|
|
@ -1122,7 +1152,17 @@ class MTX extends TX {
|
|||
|
||||
// Select necessary coins.
|
||||
const select = await this.selectCoins(coins, options);
|
||||
this.fill(select);
|
||||
return select;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill transaction with the selected inputs.
|
||||
* @param {CoinSelector} select
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
fill(select) {
|
||||
// Make sure we empty the input array.
|
||||
this.inputs.length = 0;
|
||||
|
||||
|
|
@ -1153,8 +1193,6 @@ class MTX extends TX {
|
|||
this.changeIndex = this.outputs.length - 1;
|
||||
assert.strictEqual(this.getFee(), select.fee);
|
||||
}
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1175,7 +1213,14 @@ class MTX extends TX {
|
|||
const inputs = [];
|
||||
/** @type {Output[]} */
|
||||
const outputs = [];
|
||||
// [Input, Output][]
|
||||
|
||||
/**
|
||||
* @typedef {Array} Linked
|
||||
* @property {Input} 0
|
||||
* @property {Output} 1
|
||||
*/
|
||||
|
||||
/** @type {Linked[]} */
|
||||
const linked = [];
|
||||
|
||||
let i = 0;
|
||||
|
|
@ -1411,525 +1456,26 @@ class MTX extends TX {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Coin Selector
|
||||
* @alias module:primitives.CoinSelector
|
||||
*/
|
||||
|
||||
class CoinSelector {
|
||||
/**
|
||||
* Create a coin selector.
|
||||
* @constructor
|
||||
* @param {MTX} tx
|
||||
* @param {Object?} options
|
||||
*/
|
||||
|
||||
constructor(tx, options) {
|
||||
this.tx = tx.clone();
|
||||
this.view = tx.view;
|
||||
this.coins = [];
|
||||
this.outputValue = 0;
|
||||
this.index = 0;
|
||||
this.chosen = [];
|
||||
this.change = 0;
|
||||
this.fee = CoinSelector.MIN_FEE;
|
||||
|
||||
this.selection = 'value';
|
||||
this.subtractFee = false;
|
||||
this.subtractIndex = -1;
|
||||
this.height = -1;
|
||||
this.depth = -1;
|
||||
this.hardFee = -1;
|
||||
this.rate = CoinSelector.FEE_RATE;
|
||||
this.maxFee = -1;
|
||||
this.round = false;
|
||||
this.coinbaseMaturity = 400;
|
||||
this.changeAddress = null;
|
||||
this.inputs = new BufferMap();
|
||||
|
||||
// Needed for size estimation.
|
||||
this.estimate = null;
|
||||
|
||||
this.injectInputs();
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize selector options.
|
||||
* @param {Object} options
|
||||
* @private
|
||||
*/
|
||||
|
||||
fromOptions(options) {
|
||||
if (options.selection) {
|
||||
assert(typeof options.selection === 'string');
|
||||
this.selection = options.selection;
|
||||
}
|
||||
|
||||
if (options.subtractFee != null) {
|
||||
if (typeof options.subtractFee === 'number') {
|
||||
assert(Number.isSafeInteger(options.subtractFee));
|
||||
assert(options.subtractFee >= -1);
|
||||
this.subtractIndex = options.subtractFee;
|
||||
this.subtractFee = this.subtractIndex !== -1;
|
||||
} else {
|
||||
assert(typeof options.subtractFee === 'boolean');
|
||||
this.subtractFee = options.subtractFee;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.subtractIndex != null) {
|
||||
assert(Number.isSafeInteger(options.subtractIndex));
|
||||
assert(options.subtractIndex >= -1);
|
||||
this.subtractIndex = options.subtractIndex;
|
||||
this.subtractFee = this.subtractIndex !== -1;
|
||||
}
|
||||
|
||||
if (options.height != null) {
|
||||
assert(Number.isSafeInteger(options.height));
|
||||
assert(options.height >= -1);
|
||||
this.height = options.height;
|
||||
}
|
||||
|
||||
if (options.confirmations != null) {
|
||||
assert(Number.isSafeInteger(options.confirmations));
|
||||
assert(options.confirmations >= -1);
|
||||
this.depth = options.confirmations;
|
||||
}
|
||||
|
||||
if (options.depth != null) {
|
||||
assert(Number.isSafeInteger(options.depth));
|
||||
assert(options.depth >= -1);
|
||||
this.depth = options.depth;
|
||||
}
|
||||
|
||||
if (options.hardFee != null) {
|
||||
assert(Number.isSafeInteger(options.hardFee));
|
||||
assert(options.hardFee >= -1);
|
||||
this.hardFee = options.hardFee;
|
||||
}
|
||||
|
||||
if (options.rate != null) {
|
||||
assert(Number.isSafeInteger(options.rate));
|
||||
assert(options.rate >= 0);
|
||||
this.rate = options.rate;
|
||||
}
|
||||
|
||||
if (options.maxFee != null) {
|
||||
assert(Number.isSafeInteger(options.maxFee));
|
||||
assert(options.maxFee >= -1);
|
||||
this.maxFee = options.maxFee;
|
||||
}
|
||||
|
||||
if (options.round != null) {
|
||||
assert(typeof options.round === 'boolean');
|
||||
this.round = options.round;
|
||||
}
|
||||
|
||||
if (options.coinbaseMaturity != null) {
|
||||
assert((options.coinbaseMaturity >>> 0) === options.coinbaseMaturity);
|
||||
this.coinbaseMaturity = options.coinbaseMaturity;
|
||||
}
|
||||
|
||||
if (options.changeAddress) {
|
||||
const addr = options.changeAddress;
|
||||
if (typeof addr === 'string') {
|
||||
this.changeAddress = Address.fromString(addr);
|
||||
} else {
|
||||
assert(addr instanceof Address);
|
||||
this.changeAddress = addr;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.estimate) {
|
||||
assert(typeof options.estimate === 'function');
|
||||
this.estimate = options.estimate;
|
||||
}
|
||||
|
||||
if (options.inputs) {
|
||||
assert(Array.isArray(options.inputs));
|
||||
|
||||
const lastIndex = this.inputs.size;
|
||||
for (let i = 0; i < options.inputs.length; i++) {
|
||||
const prevout = options.inputs[i];
|
||||
assert(prevout && typeof prevout === 'object');
|
||||
const {hash, index} = prevout;
|
||||
this.inputs.set(Outpoint.toKey(hash, index), lastIndex + i);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to inject existing inputs.
|
||||
* @private
|
||||
*/
|
||||
|
||||
injectInputs() {
|
||||
if (this.tx.inputs.length > 0) {
|
||||
for (let i = 0; i < this.tx.inputs.length; i++) {
|
||||
const {prevout} = this.tx.inputs[i];
|
||||
this.inputs.set(prevout.toKey(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the selector with coins to select from.
|
||||
* @param {Coin[]} coins
|
||||
*/
|
||||
|
||||
init(coins) {
|
||||
this.coins = coins.slice();
|
||||
this.outputValue = this.tx.getOutputValue();
|
||||
this.index = 0;
|
||||
this.chosen = [];
|
||||
this.change = 0;
|
||||
this.fee = CoinSelector.MIN_FEE;
|
||||
this.tx.inputs.length = 0;
|
||||
|
||||
switch (this.selection) {
|
||||
case 'all':
|
||||
case 'random':
|
||||
this.coins.sort(sortRandom);
|
||||
break;
|
||||
case 'age':
|
||||
this.coins.sort(sortAge);
|
||||
break;
|
||||
case 'value':
|
||||
this.coins.sort(sortValue);
|
||||
break;
|
||||
default:
|
||||
throw new FundingError(`Bad selection type: ${this.selection}.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total value required.
|
||||
* @returns {AmountValue}
|
||||
*/
|
||||
|
||||
total() {
|
||||
if (this.subtractFee)
|
||||
return this.outputValue;
|
||||
return this.outputValue + this.fee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the selector has
|
||||
* completely funded the transaction.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
isFull() {
|
||||
return this.tx.getInputValue() >= this.total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a coin is spendable
|
||||
* with regards to the options.
|
||||
* @param {Coin} coin
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
isSpendable(coin) {
|
||||
if (this.tx.view.hasEntry(coin))
|
||||
return false;
|
||||
|
||||
if (coin.covenant.isNonspendable())
|
||||
return false;
|
||||
|
||||
if (this.height === -1)
|
||||
return true;
|
||||
|
||||
if (coin.coinbase) {
|
||||
if (coin.height === -1)
|
||||
return false;
|
||||
|
||||
if (this.height + 1 < coin.height + this.coinbaseMaturity)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.depth === -1)
|
||||
return true;
|
||||
|
||||
const depth = coin.getDepth(this.height);
|
||||
|
||||
if (depth < this.depth)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current fee based on a size.
|
||||
* @param {Number} size
|
||||
* @returns {AmountValue}
|
||||
*/
|
||||
|
||||
getFee(size) {
|
||||
// This is mostly here for testing.
|
||||
// i.e. A fee rounded to the nearest
|
||||
// kb is easier to predict ahead of time.
|
||||
if (this.round)
|
||||
return policy.getRoundFee(size, this.rate);
|
||||
|
||||
return policy.getMinFee(size, this.rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fund the transaction with more
|
||||
* coins if the `output value + fee`
|
||||
* total was updated.
|
||||
*/
|
||||
|
||||
fund() {
|
||||
// Ensure all preferred inputs first.
|
||||
this.resolveInputCoins();
|
||||
|
||||
if (this.isFull())
|
||||
return;
|
||||
|
||||
while (this.index < this.coins.length) {
|
||||
const coin = this.coins[this.index++];
|
||||
|
||||
if (!this.isSpendable(coin))
|
||||
continue;
|
||||
|
||||
this.tx.addCoin(coin);
|
||||
this.chosen.push(coin);
|
||||
|
||||
if (this.selection === 'all')
|
||||
continue;
|
||||
|
||||
if (this.isFull())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate selection from `coins`.
|
||||
* @param {Coin[]} coins
|
||||
* @returns {Promise<CoinSelector>}
|
||||
*/
|
||||
|
||||
async select(coins) {
|
||||
this.init(coins);
|
||||
|
||||
if (this.hardFee !== -1) {
|
||||
this.selectHard();
|
||||
} else {
|
||||
// This is potentially asynchronous:
|
||||
// it may invoke the size estimator
|
||||
// required for redeem scripts (we
|
||||
// may be calling out to a wallet
|
||||
// or something similar).
|
||||
await this.selectEstimate();
|
||||
}
|
||||
|
||||
if (!this.isFull()) {
|
||||
// Still failing to get enough funds.
|
||||
throw new FundingError(
|
||||
'Not enough funds.',
|
||||
this.tx.getInputValue(),
|
||||
this.total());
|
||||
}
|
||||
|
||||
// How much money is left after filling outputs.
|
||||
this.change = this.tx.getInputValue() - this.total();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize selection based on size estimate.
|
||||
*/
|
||||
|
||||
async selectEstimate() {
|
||||
// Set minimum fee and do
|
||||
// an initial round of funding.
|
||||
this.fee = CoinSelector.MIN_FEE;
|
||||
this.fund();
|
||||
|
||||
// Add dummy output for change.
|
||||
const change = new Output();
|
||||
|
||||
if (this.changeAddress) {
|
||||
change.address = this.changeAddress;
|
||||
} else {
|
||||
// In case we don't have a change address,
|
||||
// we use a fake p2pkh output to gauge size.
|
||||
change.address.fromPubkeyhash(Buffer.allocUnsafe(20));
|
||||
}
|
||||
|
||||
this.tx.outputs.push(change);
|
||||
|
||||
// Keep recalculating the fee and funding
|
||||
// until we reach some sort of equilibrium.
|
||||
do {
|
||||
const size = await this.tx.estimateSize(this.estimate);
|
||||
|
||||
this.fee = this.getFee(size);
|
||||
|
||||
if (this.maxFee > 0 && this.fee > this.maxFee)
|
||||
throw new FundingError('Fee is too high.');
|
||||
|
||||
// Failed to get enough funds, add more coins.
|
||||
if (!this.isFull())
|
||||
this.fund();
|
||||
} while (!this.isFull() && this.index < this.coins.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate selection based on a hard fee.
|
||||
*/
|
||||
|
||||
selectHard() {
|
||||
this.fee = this.hardFee;
|
||||
this.fund();
|
||||
}
|
||||
|
||||
resolveInputCoins() {
|
||||
if (this.inputs.size === 0)
|
||||
return;
|
||||
|
||||
const coins = [];
|
||||
|
||||
for (let i = 0 ; i < this.inputs.size; i++) {
|
||||
coins.push(null);
|
||||
}
|
||||
|
||||
// first resolve from coinview if possible.
|
||||
for (const key of this.inputs.keys()) {
|
||||
const prevout = Outpoint.fromKey(key);
|
||||
|
||||
if (this.view.hasEntry(prevout)) {
|
||||
const coinEntry = this.view.getEntry(prevout);
|
||||
const i = this.inputs.get(key);
|
||||
|
||||
if (i != null) {
|
||||
assert(!coins[i]);
|
||||
coins[i] = coinEntry.toCoin(prevout);
|
||||
this.inputs.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now try to resolve from the passed coins array.
|
||||
if (this.inputs.size > 0) {
|
||||
for (const coin of this.coins) {
|
||||
const {hash, index} = coin;
|
||||
const key = Outpoint.toKey(hash, index);
|
||||
const i = this.inputs.get(key);
|
||||
|
||||
if (i != null) {
|
||||
assert(!coins[i]);
|
||||
coins[i] = coin;
|
||||
this.inputs.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.inputs.size > 0)
|
||||
throw new Error('Could not resolve preferred inputs.');
|
||||
|
||||
for (const coin of coins) {
|
||||
this.tx.addCoin(coin);
|
||||
this.chosen.push(coin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default fee rate
|
||||
* for coin selection.
|
||||
* @const {Amount}
|
||||
* @default
|
||||
*/
|
||||
|
||||
CoinSelector.FEE_RATE = 10000;
|
||||
|
||||
/**
|
||||
* Minimum fee to start with
|
||||
* during coin selection.
|
||||
* @const {Amount}
|
||||
* @default
|
||||
*/
|
||||
|
||||
CoinSelector.MIN_FEE = 10000;
|
||||
|
||||
/**
|
||||
* Funding Error
|
||||
* An error thrown from the coin selector.
|
||||
* @ignore
|
||||
* @extends Error
|
||||
* @property {String} message - Error message.
|
||||
* @property {Amount} availableFunds
|
||||
* @property {Amount} requiredFunds
|
||||
*/
|
||||
|
||||
class FundingError extends Error {
|
||||
/**
|
||||
* Create a funding error.
|
||||
* @constructor
|
||||
* @param {String} msg
|
||||
* @param {AmountValue} [available]
|
||||
* @param {AmountValue} [required]
|
||||
*/
|
||||
|
||||
constructor(msg, available, required) {
|
||||
super();
|
||||
|
||||
this.type = 'FundingError';
|
||||
this.message = msg;
|
||||
this.availableFunds = -1;
|
||||
this.requiredFunds = -1;
|
||||
|
||||
if (available != null) {
|
||||
this.message += ` (available=${Amount.coin(available)},`;
|
||||
this.message += ` required=${Amount.coin(required)})`;
|
||||
this.availableFunds = available;
|
||||
this.requiredFunds = required;
|
||||
}
|
||||
|
||||
if (Error.captureStackTrace)
|
||||
Error.captureStackTrace(this, FundingError);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function sortAge(a, b) {
|
||||
a = a.height === -1 ? 0x7fffffff : a.height;
|
||||
b = b.height === -1 ? 0x7fffffff : b.height;
|
||||
return a - b;
|
||||
}
|
||||
|
||||
function sortRandom(a, b) {
|
||||
return Math.random() > 0.5 ? 1 : -1;
|
||||
}
|
||||
|
||||
function sortValue(a, b) {
|
||||
if (a.height === -1 && b.height !== -1)
|
||||
return 1;
|
||||
|
||||
if (a.height !== -1 && b.height === -1)
|
||||
return -1;
|
||||
|
||||
return b.value - a.value;
|
||||
}
|
||||
/**
|
||||
* @param {Input} a
|
||||
* @param {Input} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
function sortInputs(a, b) {
|
||||
return a.compare(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Output} a
|
||||
* @param {Output} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
function sortOutputs(a, b) {
|
||||
return a.compare(b);
|
||||
}
|
||||
|
|
@ -1942,9 +1488,6 @@ function sortLinked(a, b) {
|
|||
* Expose
|
||||
*/
|
||||
|
||||
exports = MTX;
|
||||
exports.MTX = MTX;
|
||||
exports.Selector = CoinSelector;
|
||||
exports.FundingError = FundingError;
|
||||
MTX.MTX = MTX;
|
||||
|
||||
module.exports = exports;
|
||||
module.exports = MTX;
|
||||
|
|
|
|||
711
lib/utils/coinselector.js
Normal file
711
lib/utils/coinselector.js
Normal file
|
|
@ -0,0 +1,711 @@
|
|||
/*!
|
||||
* coinselector.js - Coin Selector
|
||||
* Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
|
||||
* Copyright (c) 2025, Nodari Chkuaselidze (MIT License)
|
||||
* https://github.com/handshake-org/hsd
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const assert = require('bsert');
|
||||
const Amount = require('../ui/amount');
|
||||
const Address = require('../primitives/address');
|
||||
const Output = require('../primitives/output');
|
||||
const Outpoint = require('../primitives/outpoint');
|
||||
const policy = require('../protocol/policy');
|
||||
const {BufferMap} = require('buffer-map');
|
||||
|
||||
/** @typedef {import('../types').Amount} AmountValue */
|
||||
/** @typedef {import('../types').Hash} Hash */
|
||||
/** @typedef {import('../coins/coinview')} CoinView */
|
||||
/** @typedef {import('../primitives/mtx').MTX} MTX */
|
||||
/** @typedef {import('../primitives/coin')} Coin */
|
||||
|
||||
class AbstractCoinSource {
|
||||
/**
|
||||
* Initialize the coin source.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async init() {
|
||||
throw new Error('Abstract method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
hasNext() {
|
||||
throw new Error('Abstract method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Coin?>}
|
||||
*/
|
||||
|
||||
next() {
|
||||
throw new Error('Abstract method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferMap<Number>} inputs
|
||||
* @param {Coin[]} coins - Coin per input.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
async resolveInputsToCoins(inputs, coins) {
|
||||
throw new Error('Abstract method.');
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {'all'|'random'|'age'|'value'} MemSelectionType */
|
||||
|
||||
/**
|
||||
* @typedef {Object} CoinSourceOptions
|
||||
* @property {MemSelectionType} [selection] - Selection type.
|
||||
* @property {Coin[]} [coins] - Coins to select from.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Coin Source with coins.
|
||||
* @alias module:utils.CoinSource
|
||||
*/
|
||||
|
||||
class InMemoryCoinSource extends AbstractCoinSource {
|
||||
/**
|
||||
* @param {CoinSourceOptions} [options]
|
||||
*/
|
||||
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
/** @type {Coin[]} */
|
||||
this.coins = [];
|
||||
|
||||
/** @type {MemSelectionType} */
|
||||
this.selection = 'value';
|
||||
|
||||
this.index = -1;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CoinSourceOptions} options
|
||||
* @returns {this}
|
||||
*/
|
||||
|
||||
fromOptions(options = {}) {
|
||||
if (options.coins != null) {
|
||||
assert(Array.isArray(options.coins), 'Coins must be an array.');
|
||||
this.coins = options.coins.slice();
|
||||
}
|
||||
|
||||
if (options.selection != null) {
|
||||
assert(typeof options.selection === 'string',
|
||||
'Selection must be a string.');
|
||||
this.selection = options.selection;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.index = 0;
|
||||
|
||||
switch (this.selection) {
|
||||
case 'all':
|
||||
case 'random':
|
||||
shuffle(this.coins);
|
||||
break;
|
||||
case 'age':
|
||||
this.coins.sort(sortAge);
|
||||
break;
|
||||
case 'value':
|
||||
this.coins.sort(sortValue);
|
||||
break;
|
||||
default:
|
||||
throw new FundingError(`Bad selection type: ${this.selection}`);
|
||||
}
|
||||
}
|
||||
|
||||
hasNext() {
|
||||
return this.index < this.coins.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Coin?>}
|
||||
*/
|
||||
|
||||
async next() {
|
||||
if (!this.hasNext())
|
||||
return null;
|
||||
|
||||
return this.coins[this.index++];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferMap<Number>} inputs
|
||||
* @param {Coin[]} coins
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
async resolveInputsToCoins(inputs, coins) {
|
||||
for (const coin of this.coins) {
|
||||
const {hash, index} = coin;
|
||||
const key = Outpoint.toKey(hash, index);
|
||||
const i = inputs.get(key);
|
||||
|
||||
if (i != null) {
|
||||
assert(!coins[i]);
|
||||
coins[i] = coin;
|
||||
inputs.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} InputOption
|
||||
* @property {Hash} hash
|
||||
* @property {Number} index
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} CoinSelectorOptions
|
||||
* @property {Address} [changeAddress] - Change address.
|
||||
* @property {Boolean} [subtractFee] - Subtract fee from output.
|
||||
* @property {Number} [subtractIndex] - Index of output to subtract fee from.
|
||||
* @property {Number} [height] - Current chain height.
|
||||
* @property {Number} [depth] - Minimum confirmation depth of coins to spend.
|
||||
* @property {Number} [confirmations] - depth alias.
|
||||
* @property {Number} [coinbaseMaturity] - When do CBs become spendable.
|
||||
* @property {Number} [hardFee] - Fixed fee.
|
||||
* @property {Number} [rate] - Rate of dollarydoo per kB.
|
||||
* @property {Number} [maxFee] - Maximum fee we are willing to pay.
|
||||
* @property {Boolean} [round] - Round to the nearest kilobyte.
|
||||
* @property {Function?} [estimate] - Input script size estimator.
|
||||
* @property {Boolean} [selectAll] - Select all coins.
|
||||
* @property {InputOption[]} [inputs] - Inputs to use for funding.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Coin Selector
|
||||
* @alias module:utils.CoinSelector
|
||||
* @property {MTX} tx - clone of the original mtx.
|
||||
* @property {CoinView} view - reference to the original view.
|
||||
*/
|
||||
|
||||
class CoinSelector {
|
||||
/**
|
||||
* @param {MTX} mtx
|
||||
* @param {AbstractCoinSource} source
|
||||
* @param {CoinSelectorOptions?} [options]
|
||||
*/
|
||||
|
||||
constructor(mtx, source, options = {}) {
|
||||
this.original = mtx;
|
||||
/** @type {MTX} */
|
||||
this.tx = mtx.clone();
|
||||
/** @type {CoinView} */
|
||||
this.view = mtx.view;
|
||||
this.source = source;
|
||||
this.outputValue = 0;
|
||||
this.fee = CoinSelector.MIN_FEE;
|
||||
|
||||
/** @type {Coin[]} */
|
||||
this.chosen = [];
|
||||
|
||||
this.selectAll = false;
|
||||
this.subtractFee = false;
|
||||
this.subtractIndex = -1;
|
||||
this.height = -1;
|
||||
this.depth = -1;
|
||||
this.hardFee = -1;
|
||||
this.rate = CoinSelector.FEE_RATE;
|
||||
this.maxFee = -1;
|
||||
this.round = false;
|
||||
this.coinbaseMaturity = 400;
|
||||
this.changeAddress = null;
|
||||
this.estimate = null;
|
||||
|
||||
/** @type {BufferMap<Number>} */
|
||||
this.inputs = new BufferMap();
|
||||
|
||||
this.injectInputs();
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CoinSelectorOptions} [options]
|
||||
* @returns {this}
|
||||
*/
|
||||
|
||||
fromOptions(options = {}) {
|
||||
if (options.subtractFee != null) {
|
||||
if (typeof options.subtractFee === 'number') {
|
||||
assert(Number.isSafeInteger(options.subtractFee));
|
||||
assert(options.subtractFee >= -1);
|
||||
this.subtractIndex = options.subtractFee;
|
||||
this.subtractFee = this.subtractIndex !== -1;
|
||||
} else {
|
||||
assert(typeof options.subtractFee === 'boolean');
|
||||
this.subtractFee = options.subtractFee;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.subtractIndex != null) {
|
||||
assert(Number.isSafeInteger(options.subtractIndex));
|
||||
assert(options.subtractIndex >= -1);
|
||||
this.subtractIndex = options.subtractIndex;
|
||||
this.subtractFee = this.subtractIndex !== -1;
|
||||
}
|
||||
|
||||
if (options.height != null) {
|
||||
assert(Number.isSafeInteger(options.height));
|
||||
assert(options.height >= -1);
|
||||
this.height = options.height;
|
||||
}
|
||||
|
||||
if (options.confirmations != null) {
|
||||
assert(Number.isSafeInteger(options.confirmations));
|
||||
assert(options.confirmations >= -1);
|
||||
this.depth = options.confirmations;
|
||||
}
|
||||
|
||||
if (options.depth != null) {
|
||||
assert(Number.isSafeInteger(options.depth));
|
||||
assert(options.depth >= -1);
|
||||
this.depth = options.depth;
|
||||
}
|
||||
|
||||
if (options.hardFee != null) {
|
||||
assert(Number.isSafeInteger(options.hardFee));
|
||||
assert(options.hardFee >= -1);
|
||||
this.hardFee = options.hardFee;
|
||||
}
|
||||
|
||||
if (options.rate != null) {
|
||||
assert(Number.isSafeInteger(options.rate));
|
||||
assert(options.rate >= 0);
|
||||
this.rate = options.rate;
|
||||
}
|
||||
|
||||
if (options.maxFee != null) {
|
||||
assert(Number.isSafeInteger(options.maxFee));
|
||||
assert(options.maxFee >= -1);
|
||||
this.maxFee = options.maxFee;
|
||||
}
|
||||
|
||||
if (options.round != null) {
|
||||
assert(typeof options.round === 'boolean');
|
||||
this.round = options.round;
|
||||
}
|
||||
|
||||
if (options.coinbaseMaturity != null) {
|
||||
assert((options.coinbaseMaturity >>> 0) === options.coinbaseMaturity);
|
||||
this.coinbaseMaturity = options.coinbaseMaturity;
|
||||
}
|
||||
|
||||
if (options.changeAddress) {
|
||||
const addr = options.changeAddress;
|
||||
if (typeof addr === 'string') {
|
||||
this.changeAddress = Address.fromString(addr);
|
||||
} else {
|
||||
assert(addr instanceof Address);
|
||||
this.changeAddress = addr;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.estimate) {
|
||||
assert(typeof options.estimate === 'function');
|
||||
this.estimate = options.estimate;
|
||||
}
|
||||
|
||||
if (options.selectAll != null) {
|
||||
assert(typeof options.selectAll === 'boolean');
|
||||
this.selectAll = options.selectAll;
|
||||
}
|
||||
|
||||
if (options.inputs) {
|
||||
assert(Array.isArray(options.inputs));
|
||||
|
||||
const lastIndex = this.inputs.size;
|
||||
for (let i = 0; i < options.inputs.length; i++) {
|
||||
const prevout = options.inputs[i];
|
||||
assert(prevout && typeof prevout === 'object');
|
||||
const {hash, index} = prevout;
|
||||
this.inputs.set(Outpoint.toKey(hash, index), lastIndex + i);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to inject existing inputs.
|
||||
* @private
|
||||
*/
|
||||
|
||||
injectInputs() {
|
||||
if (this.tx.inputs.length > 0) {
|
||||
for (let i = 0; i < this.tx.inputs.length; i++) {
|
||||
const {prevout} = this.tx.inputs[i];
|
||||
this.inputs.set(prevout.toKey(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the selector with coins to select from.
|
||||
*/
|
||||
|
||||
init() {
|
||||
this.outputValue = this.tx.getOutputValue();
|
||||
this.chosen = [];
|
||||
this.change = 0;
|
||||
this.fee = CoinSelector.MIN_FEE;
|
||||
this.tx.inputs.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total value required.
|
||||
* @returns {AmountValue}
|
||||
*/
|
||||
|
||||
total() {
|
||||
if (this.subtractFee)
|
||||
return this.outputValue;
|
||||
|
||||
return this.outputValue + this.fee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether filler
|
||||
* completely funded the transaction.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
isFull() {
|
||||
return this.tx.getInputValue() >= this.total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a coin is spendable
|
||||
* with regards to the options.
|
||||
* @param {Coin} coin
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
isSpendable(coin) {
|
||||
if (this.tx.view.hasEntry(coin))
|
||||
return false;
|
||||
|
||||
if (coin.covenant.isNonspendable())
|
||||
return false;
|
||||
|
||||
if (this.height === -1)
|
||||
return true;
|
||||
|
||||
if (coin.coinbase) {
|
||||
if (coin.height === -1)
|
||||
return false;
|
||||
|
||||
if (this.height + 1 < coin.height + this.coinbaseMaturity)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.depth === -1)
|
||||
return true;
|
||||
|
||||
const depth = coin.getDepth(this.height);
|
||||
|
||||
if (depth < this.depth)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current fee based on a size.
|
||||
* @param {Number} size
|
||||
* @returns {AmountValue}
|
||||
*/
|
||||
|
||||
getFee(size) {
|
||||
// This is mostly here for testing.
|
||||
// i.e. A fee rounded to the nearest
|
||||
// kb is easier to predict ahead of time.
|
||||
if (this.round)
|
||||
return policy.getRoundFee(size, this.rate);
|
||||
|
||||
return policy.getMinFee(size, this.rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fund the transaction with more
|
||||
* coins if the `output value + fee`
|
||||
* total was updated.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
async fund() {
|
||||
// Ensure all preferred inputs first.
|
||||
await this.resolveInputCoins();
|
||||
|
||||
if (this.isFull() && !this.selectAll)
|
||||
return;
|
||||
|
||||
for (;;) {
|
||||
const coin = await this.source.next();
|
||||
|
||||
if (!coin)
|
||||
break;
|
||||
|
||||
if (!this.isSpendable(coin))
|
||||
continue;
|
||||
|
||||
this.tx.addCoin(coin);
|
||||
this.chosen.push(coin);
|
||||
|
||||
if (this.selectAll)
|
||||
continue;
|
||||
|
||||
if (this.isFull())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize selection based on size estimate.
|
||||
*/
|
||||
|
||||
async selectEstimate() {
|
||||
// Set minimum fee and do
|
||||
// an initial round of funding.
|
||||
this.fee = CoinSelector.MIN_FEE;
|
||||
await this.fund();
|
||||
|
||||
// Add dummy output for change.
|
||||
const change = new Output();
|
||||
|
||||
if (this.changeAddress) {
|
||||
change.address = this.changeAddress;
|
||||
} else {
|
||||
// In case we don't have a change address,
|
||||
// we use a fake p2pkh output to gauge size.
|
||||
change.address.fromPubkeyhash(Buffer.allocUnsafe(20));
|
||||
}
|
||||
|
||||
this.tx.outputs.push(change);
|
||||
|
||||
// Keep recalculating the fee and funding
|
||||
// until we reach some sort of equilibrium.
|
||||
do {
|
||||
const size = await this.tx.estimateSize(this.estimate);
|
||||
|
||||
this.fee = this.getFee(size);
|
||||
|
||||
if (this.maxFee > 0 && this.fee > this.maxFee)
|
||||
throw new FundingError('Fee is too high.');
|
||||
|
||||
// Failed to get enough funds, add more coins.
|
||||
if (!this.isFull())
|
||||
await this.fund();
|
||||
} while (!this.isFull() && this.source.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect coins for the transaction.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
async selectHard() {
|
||||
this.fee = this.hardFee;
|
||||
await this.fund();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the transaction with inputs.
|
||||
* @returns {Promise<this>}
|
||||
*/
|
||||
|
||||
async select() {
|
||||
this.init();
|
||||
|
||||
if (this.hardFee !== -1) {
|
||||
await this.selectHard();
|
||||
} else {
|
||||
// This is potentially asynchronous:
|
||||
// it may invoke the size estimator
|
||||
// required for redeem scripts (we
|
||||
// may be calling out to a wallet
|
||||
// or something similar).
|
||||
await this.selectEstimate();
|
||||
}
|
||||
|
||||
if (!this.isFull()) {
|
||||
// Still failing to get enough funds.
|
||||
throw new FundingError(
|
||||
'Not enough funds.',
|
||||
this.tx.getInputValue(),
|
||||
this.total());
|
||||
}
|
||||
|
||||
// How much money is left after filling outputs.
|
||||
this.change = this.tx.getInputValue() - this.total();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async resolveInputCoins() {
|
||||
if (this.inputs.size === 0)
|
||||
return;
|
||||
|
||||
/** @type {Coin[]} */
|
||||
const coins = [];
|
||||
|
||||
for (let i = 0 ; i < this.inputs.size; i++) {
|
||||
coins.push(null);
|
||||
}
|
||||
|
||||
// first resolve from coinview if possible.
|
||||
for (const key of this.inputs.keys()) {
|
||||
const prevout = Outpoint.fromKey(key);
|
||||
|
||||
if (this.view.hasEntry(prevout)) {
|
||||
const coinEntry = this.view.getEntry(prevout);
|
||||
const i = this.inputs.get(key);
|
||||
|
||||
if (i != null) {
|
||||
assert(!coins[i]);
|
||||
coins[i] = coinEntry.toCoin(prevout);
|
||||
this.inputs.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.inputs.size > 0)
|
||||
await this.source.resolveInputsToCoins(this.inputs, coins);
|
||||
|
||||
if (this.inputs.size > 0)
|
||||
throw new Error('Could not resolve preferred inputs.');
|
||||
|
||||
for (const coin of coins) {
|
||||
this.tx.addCoin(coin);
|
||||
this.chosen.push(coin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default fee rate
|
||||
* for coin selection.
|
||||
* @const {Amount}
|
||||
* @default
|
||||
*/
|
||||
|
||||
CoinSelector.FEE_RATE = 10000;
|
||||
|
||||
/**
|
||||
* Minimum fee to start with
|
||||
* during coin selection.
|
||||
* @const {Amount}
|
||||
* @default
|
||||
*/
|
||||
|
||||
CoinSelector.MIN_FEE = 10000;
|
||||
|
||||
/**
|
||||
* Funding Error
|
||||
* An error thrown from the coin selector.
|
||||
* @ignore
|
||||
* @extends Error
|
||||
* @property {String} message - Error message.
|
||||
* @property {Amount} availableFunds
|
||||
* @property {Amount} requiredFunds
|
||||
*/
|
||||
|
||||
class FundingError extends Error {
|
||||
/**
|
||||
* Create a funding error.
|
||||
* @constructor
|
||||
* @param {String} msg
|
||||
* @param {AmountValue} [available]
|
||||
* @param {AmountValue} [required]
|
||||
*/
|
||||
|
||||
constructor(msg, available, required) {
|
||||
super();
|
||||
|
||||
this.type = 'FundingError';
|
||||
this.message = msg;
|
||||
this.availableFunds = -1;
|
||||
this.requiredFunds = -1;
|
||||
|
||||
if (available != null) {
|
||||
this.message += ` (available=${Amount.coin(available)},`;
|
||||
this.message += ` required=${Amount.coin(required)})`;
|
||||
this.availableFunds = available;
|
||||
this.requiredFunds = required;
|
||||
}
|
||||
|
||||
if (Error.captureStackTrace)
|
||||
Error.captureStackTrace(this, FundingError);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Coin} a
|
||||
* @param {Coin} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
function sortAge(a, b) {
|
||||
const ah = a.height === -1 ? 0x7fffffff : a.height;
|
||||
const bh = b.height === -1 ? 0x7fffffff : b.height;
|
||||
return ah - bh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Coin[]} coins
|
||||
* @returns {Coin[]}
|
||||
*/
|
||||
|
||||
function shuffle(coins) {
|
||||
for (let i = coins.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[coins[i], coins[j]] = [coins[j], coins[i]];
|
||||
}
|
||||
return coins;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Coin} a
|
||||
* @param {Coin} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
function sortValue(a, b) {
|
||||
if (a.height === -1 && b.height !== -1)
|
||||
return 1;
|
||||
|
||||
if (a.height !== -1 && b.height === -1)
|
||||
return -1;
|
||||
|
||||
return b.value - a.value;
|
||||
}
|
||||
|
||||
exports.AbstractCoinSource = AbstractCoinSource;
|
||||
exports.InMemoryCoinSource = InMemoryCoinSource;
|
||||
exports.CoinSelector = CoinSelector;
|
||||
exports.FundingError = FundingError;
|
||||
|
|
@ -150,3 +150,15 @@ common.sortDeps = function sortDeps(txs) {
|
|||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wallet coin selection types.
|
||||
* @enum {String}
|
||||
*/
|
||||
|
||||
common.coinSelectionTypes = {
|
||||
DB_ALL: 'db-all',
|
||||
DB_VALUE: 'db-value',
|
||||
DB_SWEEPDUST: 'db-sweepdust',
|
||||
DB_AGE: 'db-age'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1878,6 +1878,7 @@ class TransactionOptions {
|
|||
this.rate = valid.u64('rate');
|
||||
this.maxFee = valid.u64('maxFee');
|
||||
this.selection = valid.str('selection');
|
||||
this.sweepdustMinValue = valid.u64('sweepdustMinValue');
|
||||
this.smart = valid.bool('smart');
|
||||
this.account = valid.str('account');
|
||||
this.locktime = valid.u64('locktime');
|
||||
|
|
@ -1948,8 +1949,7 @@ function enforce(value, msg) {
|
|||
* Expose
|
||||
*/
|
||||
|
||||
exports = HTTP;
|
||||
exports.HTTP = HTTP;
|
||||
exports.TransactionOptions = TransactionOptions;
|
||||
HTTP.HTTP = HTTP;
|
||||
HTTP.TransactionOptions = TransactionOptions;
|
||||
|
||||
module.exports = exports;
|
||||
module.exports = HTTP;
|
||||
|
|
|
|||
|
|
@ -118,6 +118,20 @@ exports.wdb = {
|
|||
* p[hash] -> dummy (pending tx)
|
||||
* P[account][tx-hash] -> dummy (pending tx by account)
|
||||
*
|
||||
* Coin Selection
|
||||
* --------------
|
||||
* Sv[value][tx-hash][index] -> dummy (confirmed coins by Value)
|
||||
* SV[account][value][tx-hash][index] -> dummy
|
||||
* (confirmed coins by account + Value)
|
||||
*
|
||||
* Su[value][tx-hash][index] -> dummy (Unconfirmed coins by value)
|
||||
* SU[account][value][tx-hash][index] -> dummy
|
||||
* (Unconfirmed coins by account + value)
|
||||
*
|
||||
* Sh[tx-hash][index] -> dummy (coins by account + Height)
|
||||
* SH[account][height][tx-hash][index] -> dummy
|
||||
* (coins by account + Height)
|
||||
*
|
||||
* Count and Time Index
|
||||
* --------------------
|
||||
* Ol - Latest Unconfirmed Index
|
||||
|
|
@ -161,6 +175,20 @@ exports.txdb = {
|
|||
d: bdb.key('d', ['hash256', 'uint32']),
|
||||
s: bdb.key('s', ['hash256', 'uint32']),
|
||||
|
||||
// Coin Selector
|
||||
// confirmed by Value
|
||||
Sv: bdb.key('Sv', ['uint64', 'hash256', 'uint32']),
|
||||
// confirmed by account + Value
|
||||
SV: bdb.key('SV', ['uint32', 'uint64', 'hash256', 'uint32']),
|
||||
// Unconfirmed by value
|
||||
Su: bdb.key('Su', ['uint64', 'hash256', 'uint32']),
|
||||
// Unconfirmed by account + value
|
||||
SU: bdb.key('SU', ['uint32', 'uint64', 'hash256', 'uint32']),
|
||||
// by Height
|
||||
Sh: bdb.key('Sh', ['uint32', 'hash256', 'uint32']),
|
||||
// by account + Height
|
||||
SH: bdb.key('SH', ['uint32', 'uint32', 'hash256', 'uint32']),
|
||||
|
||||
// Transaction
|
||||
t: bdb.key('t', ['hash256']),
|
||||
T: bdb.key('T', ['uint32', 'hash256']),
|
||||
|
|
|
|||
|
|
@ -1352,6 +1352,226 @@ class MigrateMigrationStateV1 extends AbstractMigration {
|
|||
}
|
||||
}
|
||||
|
||||
class MigrateCoinSelection extends AbstractMigration {
|
||||
/**
|
||||
* @param {WalletMigratorOptions} options
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
/** @type {WalletMigratorOptions} */
|
||||
this.options = options;
|
||||
this.logger = options.logger.context('wallet-migration-coin-selection');
|
||||
this.db = options.db;
|
||||
this.ldb = options.ldb;
|
||||
this.layout = MigrateCoinSelection.layout();
|
||||
|
||||
this.UNCONFIRMED_HEIGHT = 0xffffffff;
|
||||
this.batchSize = 5000;
|
||||
this.progress = {
|
||||
wid: 0,
|
||||
account: 0,
|
||||
hash: consensus.ZERO_HASH,
|
||||
index: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* We always migrate.
|
||||
* @returns {Promise<MigrationType>}
|
||||
*/
|
||||
|
||||
async check() {
|
||||
return types.MIGRATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual migration
|
||||
* @param {Batch} b
|
||||
* @param {WalletMigrationContext} ctx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async migrate(b, ctx) {
|
||||
const wlayout = this.layout.wdb;
|
||||
const wids = await this.ldb.keys({
|
||||
gte: wlayout.W.min(),
|
||||
lte: wlayout.W.max(),
|
||||
parse: key => wlayout.W.decode(key)[0]
|
||||
});
|
||||
|
||||
await this.decodeProgress(ctx.state.inProgressData);
|
||||
|
||||
for (const wid of wids) {
|
||||
if (wid < this.progress.wid) {
|
||||
this.logger.debug(
|
||||
'Skipping wallet %d (%d/%d), already migrated.',
|
||||
wid, this.progress.wid, wids.length);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
'Migrating wallet %d (%d/%d)',
|
||||
wid, this.progress.wid, wids.length);
|
||||
await this.migrateWallet(wid, ctx);
|
||||
}
|
||||
|
||||
this.db.writeVersion(b, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} wid
|
||||
* @param {WalletMigrationContext} ctx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async migrateWallet(wid, ctx) {
|
||||
const txlayout = this.layout.txdb;
|
||||
const prefix = txlayout.prefix.encode(wid);
|
||||
const bucket = this.ldb.bucket(prefix);
|
||||
|
||||
const min = txlayout.C.encode(
|
||||
this.progress.account,
|
||||
this.progress.hash,
|
||||
this.progress.index
|
||||
);
|
||||
|
||||
const coinsIter = bucket.iterator({
|
||||
gte: min,
|
||||
lte: txlayout.C.max()
|
||||
});
|
||||
|
||||
let parent = bucket.batch();
|
||||
let total = 0;
|
||||
|
||||
for await (const {key} of coinsIter) {
|
||||
const [account, hash, index] = txlayout.C.decode(key);
|
||||
const rawCoin = await bucket.get(txlayout.c.encode(hash, index));
|
||||
const coin = Coin.decode(rawCoin);
|
||||
|
||||
if (coin.isUnspendable() || coin.covenant.isNonspendable())
|
||||
continue;
|
||||
|
||||
if (coin.height === -1) {
|
||||
// index coins by value
|
||||
parent.put(txlayout.Su.encode(coin.value, hash, index), null);
|
||||
parent.put(txlayout.SU.encode(account, coin.value, hash, index), null);
|
||||
|
||||
// index coins by height
|
||||
parent.put(txlayout.Sh.encode(this.UNCONFIRMED_HEIGHT, hash, index),
|
||||
null);
|
||||
|
||||
parent.put(
|
||||
txlayout.SH.encode(account, this.UNCONFIRMED_HEIGHT, hash, index),
|
||||
null
|
||||
);
|
||||
} else {
|
||||
parent.put(txlayout.Sv.encode(coin.value, hash, index), null);
|
||||
parent.put(txlayout.SV.encode(account, coin.value, hash, index), null);
|
||||
|
||||
parent.put(txlayout.Sh.encode(coin.height, hash, index), null);
|
||||
parent.put(txlayout.SH.encode(account, coin.height, hash, index), null);
|
||||
}
|
||||
|
||||
if (++total % this.batchSize === 0) {
|
||||
// save progress
|
||||
this.progress.wid = wid;
|
||||
this.progress.account = account;
|
||||
this.progress.hash = hash;
|
||||
this.progress.index = index + 1;
|
||||
|
||||
ctx.state.inProgressData = this.encodeProgress();
|
||||
ctx.writeState(parent.root());
|
||||
|
||||
await parent.write();
|
||||
parent = bucket.batch();
|
||||
}
|
||||
};
|
||||
|
||||
this.progress.wid = wid + 1;
|
||||
this.progress.account = 0;
|
||||
this.progress.hash = consensus.ZERO_HASH;
|
||||
this.progress.index = 0;
|
||||
ctx.state.inProgressData = this.encodeProgress();
|
||||
ctx.writeState(parent.root());
|
||||
await parent.write();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
encodeProgress() {
|
||||
const bw = bio.write(44);
|
||||
bw.writeU32(this.progress.wid);
|
||||
bw.writeU32(this.progress.account);
|
||||
bw.writeBytes(this.progress.hash);
|
||||
bw.writeU32(this.progress.index);
|
||||
return bw.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get migration info.
|
||||
* @param {Buffer} data
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
decodeProgress(data) {
|
||||
if (data.length === 0)
|
||||
return;
|
||||
|
||||
assert(data.length === 44);
|
||||
|
||||
const br = bio.read(data);
|
||||
this.progress.wid = br.readU32();
|
||||
this.progress.account = br.readU32();
|
||||
this.progress.hash = br.readBytes(32);
|
||||
this.progress.index = br.readU32();
|
||||
}
|
||||
|
||||
static info() {
|
||||
return {
|
||||
name: 'Wallet Coin Selection Migration',
|
||||
description: 'Reindex coins for better coin selection'
|
||||
};
|
||||
}
|
||||
|
||||
static layout() {
|
||||
return {
|
||||
wdb: {
|
||||
V: bdb.key('V'),
|
||||
|
||||
// W[wid] -> wallet id
|
||||
W: bdb.key('W', ['uint32'])
|
||||
},
|
||||
txdb: {
|
||||
prefix: bdb.key('t', ['uint32']),
|
||||
|
||||
// Coins
|
||||
c: bdb.key('c', ['hash256', 'uint32']),
|
||||
C: bdb.key('C', ['uint32', 'hash256', 'uint32']),
|
||||
d: bdb.key('d', ['hash256', 'uint32']),
|
||||
s: bdb.key('s', ['hash256', 'uint32']),
|
||||
|
||||
// confirmed by Value
|
||||
Sv: bdb.key('Sv', ['uint64', 'hash256', 'uint32']),
|
||||
// confirmed by account + Value
|
||||
SV: bdb.key('SV', ['uint32', 'uint64', 'hash256', 'uint32']),
|
||||
// Unconfirmed by value
|
||||
Su: bdb.key('Su', ['uint64', 'hash256', 'uint32']),
|
||||
// Unconfirmed by account + value
|
||||
SU: bdb.key('SU', ['uint32', 'uint64', 'hash256', 'uint32']),
|
||||
// by height
|
||||
Sh: bdb.key('Sh', ['uint32', 'hash256', 'uint32']),
|
||||
// by account + height
|
||||
SH: bdb.key('SH', ['uint32', 'uint32', 'hash256', 'uint32'])
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wallet migration results.
|
||||
* @alias module:blockchain.WalletMigrationResult
|
||||
|
|
@ -1536,7 +1756,8 @@ WalletMigrator.migrations = {
|
|||
3: MigrateTXDBBalances,
|
||||
4: MigrateBidRevealEntries,
|
||||
5: MigrateTXCountTimeIndex,
|
||||
6: MigrateMigrationStateV1
|
||||
6: MigrateMigrationStateV1,
|
||||
7: MigrateCoinSelection
|
||||
};
|
||||
|
||||
// Expose migrations
|
||||
|
|
@ -1547,5 +1768,6 @@ WalletMigrator.MigrateTXDBBalances = MigrateTXDBBalances;
|
|||
WalletMigrator.MigrateBidRevealEntries = MigrateBidRevealEntries;
|
||||
WalletMigrator.MigrateTXCountTimeIndex = MigrateTXCountTimeIndex;
|
||||
WalletMigrator.MigrateMigrationStateV1 = MigrateMigrationStateV1;
|
||||
WalletMigrator.MigrateCoinSelection = MigrateCoinSelection;
|
||||
|
||||
module.exports = WalletMigrator;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ const NodeClient = require('./nodeclient');
|
|||
const HTTP = require('./http');
|
||||
const RPC = require('./rpc');
|
||||
|
||||
/** @typedef {import('../node/fullnode')} FullNode */
|
||||
/** @typedef {import('../node/spvnode')} SPVNode */
|
||||
/** @typedef {FullNode|SPVNode} Node */
|
||||
|
||||
/**
|
||||
* @exports wallet/plugin
|
||||
*/
|
||||
|
|
@ -114,9 +118,11 @@ plugin.id = 'walletdb';
|
|||
/**
|
||||
* Plugin initialization.
|
||||
* @param {Node} node
|
||||
* @returns {WalletDB}
|
||||
* @returns {Plugin}
|
||||
*/
|
||||
|
||||
plugin.init = function init(node) {
|
||||
return new Plugin(node);
|
||||
};
|
||||
|
||||
plugin.Plugin = Plugin;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ const {EXP} = consensus;
|
|||
const RPCBase = bweb.RPC;
|
||||
const RPCError = bweb.RPCError;
|
||||
|
||||
/** @typedef {import('./node')} WalletNode */
|
||||
/** @typedef {import('./plugin').Plugin} Plugin */
|
||||
|
||||
/*
|
||||
* Constants
|
||||
*/
|
||||
|
|
@ -76,13 +79,12 @@ const MAGIC_STRING = `${pkg.currency} signed message:\n`;
|
|||
/**
|
||||
* Wallet RPC
|
||||
* @alias module:wallet.RPC
|
||||
* @extends bweb.RPC
|
||||
*/
|
||||
|
||||
class RPC extends RPCBase {
|
||||
/**
|
||||
* Create an RPC.
|
||||
* @param {WalletDB} wdb
|
||||
* @param {WalletNode|Plugin} node
|
||||
*/
|
||||
|
||||
constructor(node) {
|
||||
|
|
|
|||
|
|
@ -197,6 +197,86 @@ class TXDB {
|
|||
b.del(layout.d.encode(spender.hash, spender.index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Coin selection index credit.
|
||||
* @param {Batch} b
|
||||
* @param {Credit} credit
|
||||
* @param {Path} path
|
||||
* @param {Number?} oldHeight
|
||||
*/
|
||||
|
||||
indexCSCredit(b, credit, path, oldHeight) {
|
||||
const {coin} = credit;
|
||||
|
||||
if (coin.isUnspendable() || coin.covenant.isNonspendable())
|
||||
return;
|
||||
|
||||
// value index
|
||||
if (coin.height === -1) {
|
||||
// index unconfirmed coin by value
|
||||
b.put(layout.Su.encode(coin.value, coin.hash, coin.index), null);
|
||||
|
||||
// index unconfirmed coin by account + value.
|
||||
b.put(layout.SU.encode(
|
||||
path.account, coin.value, coin.hash, coin.index), null);
|
||||
} else {
|
||||
// index confirmed coin by value
|
||||
b.put(layout.Sv.encode(coin.value, coin.hash, coin.index), null);
|
||||
|
||||
// index confirmed coin by account + value.
|
||||
b.put(layout.SV.encode(
|
||||
path.account, coin.value, coin.hash, coin.index), null);
|
||||
}
|
||||
|
||||
// cleanup old value indexes.
|
||||
if (oldHeight && oldHeight === -1) {
|
||||
// remove unconfirmed indexes, now that it's confirmed.
|
||||
b.del(layout.Su.encode(coin.value, coin.hash, coin.index));
|
||||
b.del(layout.SU.encode(path.account, coin.value, coin.hash, coin.index));
|
||||
} else if (oldHeight && oldHeight !== -1) {
|
||||
// remove confirmed indexes, now that it's unconfirmed.
|
||||
b.del(layout.Sv.encode(coin.value, coin.hash, coin.index));
|
||||
b.del(layout.SV.encode(path.account, coin.value, coin.hash, coin.index));
|
||||
}
|
||||
|
||||
// handle height indexes
|
||||
// index coin by account + height
|
||||
const height = coin.height === -1 ? UNCONFIRMED_HEIGHT : coin.height;
|
||||
b.put(layout.Sh.encode(height, coin.hash, coin.index), null);
|
||||
b.put(layout.SH.encode(path.account, height, coin.hash, coin.index), null);
|
||||
|
||||
if (oldHeight != null) {
|
||||
const height = oldHeight === -1 ? UNCONFIRMED_HEIGHT : oldHeight;
|
||||
b.del(layout.Sh.encode(height, coin.hash, coin.index));
|
||||
b.del(layout.SH.encode(path.account, height, coin.hash, coin.index));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unindex Credit.
|
||||
* @param {Batch} b
|
||||
* @param {Credit} credit
|
||||
* @param {Path} path
|
||||
*/
|
||||
|
||||
unindexCSCredit(b, credit, path) {
|
||||
const {coin} = credit;
|
||||
|
||||
// Remove coin by account + value.
|
||||
if (coin.height === -1) {
|
||||
b.del(layout.Su.encode(coin.value, coin.hash, coin.index));
|
||||
b.del(layout.SU.encode(path.account, coin.value, coin.hash, coin.index));
|
||||
} else {
|
||||
b.del(layout.Sv.encode(coin.value, coin.hash, coin.index));
|
||||
b.del(layout.SV.encode(path.account, coin.value, coin.hash, coin.index));
|
||||
}
|
||||
|
||||
// Remove coin by account + height
|
||||
const height = coin.height === -1 ? UNCONFIRMED_HEIGHT : coin.height;
|
||||
b.del(layout.Sh.encode(height, coin.hash, coin.index));
|
||||
b.del(layout.SH.encode(path.account, height, coin.hash, coin.index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Spend credit by spender/input record.
|
||||
* Add undo coin to the input record.
|
||||
|
|
@ -1118,6 +1198,7 @@ class TXDB {
|
|||
this.unlockBalances(state, credit, path, height);
|
||||
|
||||
await this.removeCredit(b, credit, path);
|
||||
this.unindexCSCredit(b, credit, path);
|
||||
|
||||
view.addCoin(coin);
|
||||
}
|
||||
|
|
@ -1162,6 +1243,7 @@ class TXDB {
|
|||
}
|
||||
|
||||
await this.saveCredit(b, credit, path);
|
||||
this.indexCSCredit(b, credit, path, null);
|
||||
await this.watchOpensEarly(b, output);
|
||||
}
|
||||
|
||||
|
|
@ -1340,6 +1422,7 @@ class TXDB {
|
|||
// entirely, now that we know it's also
|
||||
// been removed on-chain.
|
||||
await this.removeCredit(b, credit, path);
|
||||
this.unindexCSCredit(b, credit, path);
|
||||
|
||||
view.addCoin(coin);
|
||||
}
|
||||
|
|
@ -1397,6 +1480,7 @@ class TXDB {
|
|||
credit.coin.height = height;
|
||||
|
||||
await this.saveCredit(b, credit, path);
|
||||
this.indexCSCredit(b, credit, path, -1);
|
||||
}
|
||||
|
||||
// Handle names.
|
||||
|
|
@ -1521,6 +1605,7 @@ class TXDB {
|
|||
|
||||
credit.spent = false;
|
||||
await this.saveCredit(b, credit, path);
|
||||
this.indexCSCredit(b, credit, path, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1553,6 +1638,7 @@ class TXDB {
|
|||
}
|
||||
|
||||
await this.removeCredit(b, credit, path);
|
||||
this.unindexCSCredit(b, credit, path);
|
||||
}
|
||||
|
||||
// Undo name state.
|
||||
|
|
@ -1783,6 +1869,7 @@ class TXDB {
|
|||
credit.spent = true;
|
||||
own = true;
|
||||
await this.saveCredit(b, credit, path);
|
||||
this.indexCSCredit(b, credit, path, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1834,6 +1921,7 @@ class TXDB {
|
|||
|
||||
// Update coin height and confirmed
|
||||
// balance. Save once again.
|
||||
const oldHeight = credit.coin.height;
|
||||
credit.coin.height = -1;
|
||||
|
||||
// If the coin was not discovered now, it means
|
||||
|
|
@ -1846,6 +1934,7 @@ class TXDB {
|
|||
}
|
||||
|
||||
await this.saveCredit(b, credit, path);
|
||||
this.indexCSCredit(b, credit, path, oldHeight);
|
||||
}
|
||||
|
||||
// Unconfirm will also index OPENs as the transaction is now part of the
|
||||
|
|
@ -2199,13 +2288,13 @@ class TXDB {
|
|||
|
||||
const time = medianTime;
|
||||
b.del(layout.Oi.encode(time, count.height,
|
||||
count.index, options.hash));
|
||||
count.index, options.hash));
|
||||
|
||||
for (const [acct] of accounts) {
|
||||
b.del(layout.OT.encode(acct, count.height, count.index));
|
||||
|
||||
b.del(layout.OI.encode(acct, time, count.height,
|
||||
count.index, options.hash));
|
||||
count.index, options.hash));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3372,8 +3461,8 @@ class TXDB {
|
|||
* Filter array of coins or outpoints
|
||||
* for only unlocked ones.
|
||||
* jsdoc can't express this type.
|
||||
* @param {Coin[]|Outpoint[]} coins
|
||||
* @returns {Coin[]|Outpoint[]}
|
||||
* @param {Coin[]} coins
|
||||
* @returns {Coin[]}
|
||||
*/
|
||||
|
||||
filterLocked(coins) {
|
||||
|
|
@ -3726,6 +3815,179 @@ class TXDB {
|
|||
return coins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credits iterator sorted by value.
|
||||
* @param {Number} acct
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.minValue=0]
|
||||
* @param {Number} [options.maxValue=MAX_MONEY]
|
||||
* @param {Number?} [options.limit]
|
||||
* @param {Boolean} [options.reverse=false]
|
||||
* @param {Boolean} [options.inclusive=true]
|
||||
* @returns {AsyncGenerator<Credit>}
|
||||
*/
|
||||
|
||||
async *getAccountCreditIterByValue(acct, options = {}) {
|
||||
assert(typeof acct === 'number');
|
||||
assert(options && typeof options === 'object');
|
||||
|
||||
const {
|
||||
minValue = 0,
|
||||
maxValue = consensus.MAX_MONEY,
|
||||
inclusive = true,
|
||||
reverse = false,
|
||||
limit
|
||||
} = options;
|
||||
|
||||
assert(typeof minValue === 'number');
|
||||
assert(typeof maxValue === 'number');
|
||||
assert(minValue <= maxValue);
|
||||
|
||||
const iterOpts = {
|
||||
limit: limit,
|
||||
reverse: reverse,
|
||||
keys: true,
|
||||
values: false
|
||||
};
|
||||
|
||||
const greater = inclusive ? 'gte' : 'gt';
|
||||
const lesser = inclusive ? 'lte' : 'lt';
|
||||
|
||||
let prefix, hashIdx;
|
||||
let min, max;
|
||||
|
||||
if (acct === -1) {
|
||||
prefix = layout.Sv;
|
||||
min = prefix.min(minValue);
|
||||
max = prefix.max(maxValue);
|
||||
hashIdx = 1;
|
||||
} else {
|
||||
prefix = layout.SV;
|
||||
min = prefix.min(acct, minValue);
|
||||
max = prefix.max(acct, maxValue);
|
||||
hashIdx = 2;
|
||||
}
|
||||
|
||||
iterOpts[greater] = min;
|
||||
iterOpts[lesser] = max;
|
||||
|
||||
const iter = this.bucket.iterator(iterOpts);
|
||||
let items = 0;
|
||||
|
||||
for await (const key of iter.keysAsync()) {
|
||||
const decoded = prefix.decode(key);
|
||||
const hash = decoded[hashIdx];
|
||||
const index = decoded[hashIdx + 1];
|
||||
const credit = await this.getCredit(hash, index);
|
||||
|
||||
assert(credit);
|
||||
yield credit;
|
||||
items++;
|
||||
}
|
||||
|
||||
// now process unconfirmed.
|
||||
if (acct === -1) {
|
||||
prefix = layout.Su;
|
||||
min = prefix.min(minValue);
|
||||
max = prefix.max(maxValue);
|
||||
} else {
|
||||
prefix = layout.SU;
|
||||
min = prefix.min(acct, minValue);
|
||||
max = prefix.max(acct, maxValue);
|
||||
}
|
||||
|
||||
iterOpts[greater] = min;
|
||||
iterOpts[lesser] = max;
|
||||
|
||||
if (limit != null && limit > 0) {
|
||||
if (items >= limit)
|
||||
return;
|
||||
|
||||
iterOpts.limit = limit - items;
|
||||
}
|
||||
|
||||
const ucIter = this.bucket.iterator(iterOpts);
|
||||
|
||||
for await (const key of ucIter.keysAsync()) {
|
||||
const decoded = prefix.decode(key);
|
||||
const hash = decoded[hashIdx];
|
||||
const index = decoded[hashIdx + 1];
|
||||
const credit = await this.getCredit(hash, index);
|
||||
|
||||
assert(credit);
|
||||
yield credit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credits iterator sorted by height.
|
||||
* @param {Number} acct
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.minHeight=0]
|
||||
* @param {Number} [options.maxHeight=UNCONFIRMED_HEIGHT]
|
||||
* @param {Number} [options.limit=-1]
|
||||
* @param {Boolean} [options.reverse=false]
|
||||
* @param {Boolean} [options.inclusive=true]
|
||||
* @returns {AsyncGenerator<Credit>}
|
||||
*/
|
||||
|
||||
async *getAccountCreditIterByHeight(acct, options = {}) {
|
||||
assert(typeof acct === 'number');
|
||||
assert(options && typeof options === 'object');
|
||||
|
||||
const {
|
||||
minHeight = 0,
|
||||
maxHeight = UNCONFIRMED_HEIGHT,
|
||||
inclusive = true,
|
||||
reverse = false,
|
||||
limit
|
||||
} = options;
|
||||
|
||||
assert(typeof minHeight === 'number');
|
||||
assert(typeof maxHeight === 'number');
|
||||
assert(minHeight <= maxHeight);
|
||||
|
||||
const iterOpts = {
|
||||
limit,
|
||||
reverse,
|
||||
keys: true,
|
||||
values: false
|
||||
};
|
||||
|
||||
const greater = inclusive ? 'gte' : 'gt';
|
||||
const lesser = inclusive ? 'lte' : 'lt';
|
||||
|
||||
let prefix, hashIdx;
|
||||
let min, max;
|
||||
|
||||
if (acct === -1) {
|
||||
prefix = layout.Sh;
|
||||
min = prefix.min(minHeight);
|
||||
max = prefix.max(maxHeight);
|
||||
hashIdx = 1;
|
||||
} else {
|
||||
prefix = layout.SH;
|
||||
min = prefix.min(acct, minHeight);
|
||||
max = prefix.max(acct, maxHeight);
|
||||
hashIdx = 2;
|
||||
}
|
||||
|
||||
iterOpts[greater] = min;
|
||||
iterOpts[lesser] = max;
|
||||
|
||||
const iter = this.bucket.iterator(iterOpts);
|
||||
|
||||
for await (const key of iter.keysAsync()) {
|
||||
const decoded = prefix.decode(key);
|
||||
const hash = decoded[hashIdx];
|
||||
const index = decoded[hashIdx + 1];
|
||||
const credit = await this.getCredit(hash, index);
|
||||
|
||||
assert(credit);
|
||||
yield credit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a coin viewpoint.
|
||||
* @param {TX} tx
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ const base58 = require('bcrypto/lib/encoding/base58');
|
|||
const bio = require('bufio');
|
||||
const blake2b = require('bcrypto/lib/blake2b');
|
||||
const cleanse = require('bcrypto/lib/cleanse');
|
||||
const bufmap = require('buffer-map');
|
||||
const BufferSet = bufmap.BufferSet;
|
||||
const TXDB = require('./txdb');
|
||||
const Path = require('./path');
|
||||
const common = require('./common');
|
||||
|
|
@ -38,9 +40,12 @@ const reserved = require('../covenants/reserved');
|
|||
const {ownership} = require('../covenants/ownership');
|
||||
const {states} = require('../covenants/namestate');
|
||||
const {types} = rules;
|
||||
const {BufferSet} = require('buffer-map');
|
||||
const Coin = require('../primitives/coin');
|
||||
const Outpoint = require('../primitives/outpoint');
|
||||
const {
|
||||
AbstractCoinSource,
|
||||
CoinSelector
|
||||
} = require('../utils/coinselector');
|
||||
|
||||
/** @typedef {import('bdb').DB} DB */
|
||||
/** @typedef {ReturnType<DB['batch']>} Batch */
|
||||
|
|
@ -68,6 +73,8 @@ const Outpoint = require('../primitives/outpoint');
|
|||
|
||||
const EMPTY = Buffer.alloc(0);
|
||||
|
||||
const DEFAULT_SELECTION = common.coinSelectionTypes.DB_VALUE;
|
||||
|
||||
/**
|
||||
* @typedef {Object} AddResult
|
||||
* @property {Details} details
|
||||
|
|
@ -1201,7 +1208,9 @@ class Wallet extends EventEmitter {
|
|||
* @param {(String|Number)?} options.account - If no account is
|
||||
* specified, coins from the entire wallet will be filled.
|
||||
* @param {String?} options.selection - Coin selection priority. Can
|
||||
* be `age`, `random`, or `all`. (default=age).
|
||||
* be `random`, `age`, `db-age`, `value`, `db-value`, `all`, `db-all`
|
||||
* or `db-sweepdust`
|
||||
* (default=`db-value`)
|
||||
* @param {Boolean} options.round - Whether to round to the nearest
|
||||
* kilobyte for fee calculation.
|
||||
* See {@link TX#getMinFee} vs. {@link TX#getRoundFee}.
|
||||
|
|
@ -1213,6 +1222,8 @@ class Wallet extends EventEmitter {
|
|||
* calculating one.
|
||||
* @param {Number|Boolean} options.subtractFee - Whether to subtract the
|
||||
* fee from existing outputs rather than adding more inputs.
|
||||
* @param {Number} [options.sweepdustMinValue=1] - Minimum value for
|
||||
* sweepdust value.
|
||||
* @param {Boolean} [force]
|
||||
*/
|
||||
|
||||
|
|
@ -1227,19 +1238,14 @@ class Wallet extends EventEmitter {
|
|||
|
||||
/**
|
||||
* Fill a transaction with inputs without a lock.
|
||||
* @private
|
||||
* @see MTX#selectCoins
|
||||
* @see MTX#fill
|
||||
* @param {MTX} mtx
|
||||
* @param {Object} [options]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async fill(mtx, options) {
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
async fill(mtx, options = {}) {
|
||||
const acct = options.account || 0;
|
||||
const change = await this.changeAddress(acct);
|
||||
const change = await this.changeAddress(acct === -1 ? 0 : acct);
|
||||
|
||||
if (!change)
|
||||
throw new Error('Account not found.');
|
||||
|
|
@ -1248,19 +1254,14 @@ class Wallet extends EventEmitter {
|
|||
if (rate == null)
|
||||
rate = await this.wdb.estimateFee(options.blocks);
|
||||
|
||||
let coins = options.coins || [];
|
||||
assert(Array.isArray(coins));
|
||||
if (options.smart) {
|
||||
const smartCoins = await this.getSmartCoins(options.account);
|
||||
coins = coins.concat(smartCoins);
|
||||
} else {
|
||||
let availableCoins = await this.getCoins(options.account);
|
||||
availableCoins = this.txdb.filterLocked(availableCoins);
|
||||
coins = coins.concat(availableCoins);
|
||||
}
|
||||
|
||||
await mtx.fund(coins, {
|
||||
const selected = await this.select(mtx, {
|
||||
// we use options.account to maintain the same behaviour
|
||||
account: await this.ensureIndex(options.account),
|
||||
smart: options.smart,
|
||||
selection: options.selection,
|
||||
// sweepdust options
|
||||
sweepdustMinValue: options.sweepdustMinValue,
|
||||
|
||||
round: options.round,
|
||||
depth: options.depth,
|
||||
hardFee: options.hardFee,
|
||||
|
|
@ -1273,6 +1274,53 @@ class Wallet extends EventEmitter {
|
|||
maxFee: options.maxFee,
|
||||
estimate: prev => this.estimateSize(prev)
|
||||
});
|
||||
|
||||
mtx.fill(selected);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select coins for the transaction.
|
||||
* @param {MTX} mtx
|
||||
* @param {Object} [options]
|
||||
* @returns {Promise<CoinSelector>}
|
||||
*/
|
||||
|
||||
async select(mtx, options) {
|
||||
const selection = options.selection || DEFAULT_SELECTION;
|
||||
|
||||
switch (selection) {
|
||||
case 'all':
|
||||
case 'random':
|
||||
case 'value':
|
||||
case 'age': {
|
||||
let coins = options.coins || [];
|
||||
|
||||
assert(Array.isArray(coins));
|
||||
if (options.smart) {
|
||||
const smartCoins = await this.getSmartCoins(options.account);
|
||||
coins = coins.concat(smartCoins);
|
||||
} else {
|
||||
let availableCoins = await this.getCoins(options.account);
|
||||
availableCoins = this.txdb.filterLocked(availableCoins);
|
||||
coins = coins.concat(availableCoins);
|
||||
}
|
||||
|
||||
return mtx.selectCoins(coins, options);
|
||||
}
|
||||
}
|
||||
|
||||
const source = new WalletCoinSource(this, options);
|
||||
await source.init();
|
||||
|
||||
if (selection === common.coinSelectionTypes.DB_ALL)
|
||||
options.selectAll = true;
|
||||
|
||||
const selector = new CoinSelector(mtx, source, options);
|
||||
await selector.select();
|
||||
await source.end();
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1968,9 +2016,6 @@ class Wallet extends EventEmitter {
|
|||
const nameHash = bidOutput.covenant.getHash(0);
|
||||
const height = bidOutput.covenant.getU32(1);
|
||||
|
||||
const coins = [];
|
||||
coins.push(bidCoin);
|
||||
|
||||
const blind = bidOutput.covenant.getHash(3);
|
||||
const bv = await this.getBlind(blind);
|
||||
if (!bv)
|
||||
|
|
@ -1983,10 +2028,11 @@ class Wallet extends EventEmitter {
|
|||
output.value = value;
|
||||
output.covenant.setReveal(nameHash, height, nonce);
|
||||
|
||||
reveal.addOutpoint(Outpoint.fromTX(bid, bidOuputIndex));
|
||||
reveal.addCoin(bidCoin);
|
||||
reveal.outputs.push(output);
|
||||
|
||||
await this.fill(reveal, { ...options, coins: coins });
|
||||
await this.fill(reveal, { ...options });
|
||||
|
||||
assert(
|
||||
reveal.inputs.length === 1,
|
||||
'Pre-signed REVEAL must not require additional inputs'
|
||||
|
|
@ -2171,7 +2217,6 @@ class Wallet extends EventEmitter {
|
|||
continue;
|
||||
|
||||
const ns = await this.getNameState(nameHash);
|
||||
const name = ns.name;
|
||||
|
||||
if (!ns)
|
||||
continue;
|
||||
|
|
@ -2195,7 +2240,7 @@ class Wallet extends EventEmitter {
|
|||
const bv = await this.getBlind(blind);
|
||||
|
||||
if (!bv) {
|
||||
this.logger.warning(`Blind value not found for name: ${name}.`);
|
||||
this.logger.warning(`Blind value not found for name: ${ns.name}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -5151,6 +5196,36 @@ class Wallet extends EventEmitter {
|
|||
return this.txdb.getCredits(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credits iterator sorted by value.
|
||||
* @param {Number} acct
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.minValue=0]
|
||||
* @param {Number} [options.maxValue=MAX_MONEY]
|
||||
* @param {Number} [options.limit=-1]
|
||||
* @param {Boolean} [options.reverse=false]
|
||||
* @returns {AsyncGenerator<Credit>}
|
||||
*/
|
||||
|
||||
getAccountCreditIterByValue(acct, options = {}) {
|
||||
return this.txdb.getAccountCreditIterByValue(acct, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credits iterator sorted by height.
|
||||
* @param {Number} acct
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.minHeight=0]
|
||||
* @param {Number} [options.maxHeight=UNCONFIRMED_HEIGHT]
|
||||
* @param {Number} [options.limit=-1]
|
||||
* @param {Boolean} [options.reverse=false]
|
||||
* @returns {AsyncGenerator<Credit>}
|
||||
*/
|
||||
|
||||
getAccountCreditIterByHeight(acct, options = {}) {
|
||||
return this.txdb.getAccountCreditIterByHeight(acct, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "smart" coins.
|
||||
* @param {(String|Number)?} acct
|
||||
|
|
@ -5563,6 +5638,180 @@ class Wallet extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Coin source for wallet.
|
||||
* @alias module:wallet.CoinSource
|
||||
*/
|
||||
|
||||
class WalletCoinSource extends AbstractCoinSource {
|
||||
/**
|
||||
* @param {Wallet} wallet
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
constructor(wallet, options) {
|
||||
super();
|
||||
|
||||
this.wallet = wallet;
|
||||
this.wdb = wallet.wdb;
|
||||
this.txdb = wallet.txdb;
|
||||
|
||||
this.iter = null;
|
||||
this.done = false;
|
||||
|
||||
this.account = 0;
|
||||
this.selection = DEFAULT_SELECTION;
|
||||
this.smart = false;
|
||||
|
||||
this.sweepdustMinValue = 1;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
fromOptions(options = {}) {
|
||||
if (options.account != null) {
|
||||
assert(typeof options.account === 'number',
|
||||
'Account must be a number.');
|
||||
this.account = options.account;
|
||||
}
|
||||
|
||||
if (options.selection != null) {
|
||||
assert(typeof options.selection === 'string',
|
||||
'Selection must be a string.');
|
||||
this.selection = options.selection;
|
||||
}
|
||||
|
||||
if (options.smart != null) {
|
||||
assert(typeof options.smart === 'boolean',
|
||||
'Smart must be a boolean.');
|
||||
|
||||
this.smart = options.smart;
|
||||
}
|
||||
|
||||
if (options.sweepdustMinValue != null) {
|
||||
assert(typeof options.sweepdustMinValue === 'number',
|
||||
'Sweepdust min value must be a number.');
|
||||
assert((options.sweepdustMinValue >>> 0) === options.sweepdustMinValue,
|
||||
'Sweepdust min value must be a uint32.');
|
||||
|
||||
this.sweepdustMinValue = options.sweepdustMinValue;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async init() {
|
||||
switch (this.selection) {
|
||||
case common.coinSelectionTypes.DB_VALUE:
|
||||
case common.coinSelectionTypes.DB_ALL: {
|
||||
this.iter = this.txdb.getAccountCreditIterByValue(this.account, {
|
||||
reverse: true
|
||||
});
|
||||
break;
|
||||
}
|
||||
case common.coinSelectionTypes.DB_AGE: {
|
||||
this.iter = this.txdb.getAccountCreditIterByHeight(this.account);
|
||||
break;
|
||||
}
|
||||
case common.coinSelectionTypes.DB_SWEEPDUST: {
|
||||
this.iter = this.txdb.getAccountCreditIterByValue(this.account, {
|
||||
minValue: this.sweepdustMinValue
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Invalid selection: ${this.selection}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we done.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
hasNext() {
|
||||
return !this.done;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Coin?>}
|
||||
*/
|
||||
|
||||
async next() {
|
||||
if (this.done)
|
||||
return null;
|
||||
|
||||
// look for an usable credit.
|
||||
for (;;) {
|
||||
const item = await this.iter.next();
|
||||
|
||||
if (item.done) {
|
||||
this.done = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @type {Credit} */
|
||||
const credit = item.value;
|
||||
|
||||
if (credit.spent)
|
||||
continue;
|
||||
|
||||
const {coin} = credit;
|
||||
|
||||
if (this.txdb.isLocked(coin))
|
||||
continue;
|
||||
|
||||
if (this.smart && coin.height === -1 && !credit.own)
|
||||
continue;
|
||||
|
||||
return coin;
|
||||
}
|
||||
}
|
||||
|
||||
async end() {
|
||||
if (!this.iter)
|
||||
return;
|
||||
|
||||
this.iter.return();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve coins.
|
||||
* @param {bufmap.BufferMap<Number>} inputs
|
||||
* @param {Coin[]} coins - Coin per input.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
async resolveInputsToCoins(inputs, coins) {
|
||||
for (const [key, idx] of inputs.entries()) {
|
||||
if (coins[idx] != null)
|
||||
continue;
|
||||
|
||||
const outpoint = Outpoint.fromKey(key);
|
||||
|
||||
if (this.account !== -1) {
|
||||
const hasCoin = await this.txdb.hasCoinByAccount(
|
||||
this.account,
|
||||
outpoint.hash,
|
||||
outpoint.index
|
||||
);
|
||||
|
||||
if (!hasCoin)
|
||||
continue;
|
||||
}
|
||||
|
||||
const coin = await this.txdb.getCoin(outpoint.hash, outpoint.index);
|
||||
|
||||
if (!coin)
|
||||
continue;
|
||||
|
||||
coins[idx] = coin;
|
||||
inputs.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class WalletDB extends EventEmitter {
|
|||
/** @type {bdb.DB} */
|
||||
this.db = bdb.create(this.options);
|
||||
this.name = 'wallet';
|
||||
this.version = 4;
|
||||
this.version = 5;
|
||||
|
||||
// chain state.
|
||||
this.hasStateCache = false;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const WalletDB = require('../../../lib/wallet/walletdb');
|
|||
const MTX = require('../../../lib/primitives/mtx');
|
||||
const wutils = require('../../../test/util/wallet');
|
||||
const rules = require('../../../lib/covenants/rules');
|
||||
const {deterministicInput} = require('../../../test/util/primitives');
|
||||
|
||||
const layout = {
|
||||
wdb: {
|
||||
|
|
@ -66,16 +67,16 @@ let timeCounter = 0;
|
|||
|
||||
// fund wallets
|
||||
const mtx1 = new MTX();
|
||||
mtx1.addInput(wutils.deterministicInput(txID++));
|
||||
mtx1.addInput(deterministicInput(txID++));
|
||||
mtx1.addOutput(await wallet1.receiveAddress(0), 10e6);
|
||||
|
||||
const mtx2 = new MTX();
|
||||
mtx2.addInput(wutils.deterministicInput(txID++));
|
||||
mtx2.addInput(deterministicInput(txID++));
|
||||
mtx2.addOutput(await wallet1.receiveAddress(1), 10e6);
|
||||
|
||||
// fund second wallet.
|
||||
const mtx3 = new MTX();
|
||||
mtx3.addInput(wutils.deterministicInput(txID++));
|
||||
mtx3.addInput(deterministicInput(txID++));
|
||||
mtx3.addOutput(await wallet2.receiveAddress(), 10e6);
|
||||
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), [
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ const ChainEntry = require('../../../lib/blockchain/chainentry');
|
|||
const WalletDB = require('../../../lib/wallet/walletdb');
|
||||
const MTX = require('../../../lib/primitives/mtx');
|
||||
const rules = require('../../../lib/covenants/rules');
|
||||
const wutils = require('../../../test/util/wallet');
|
||||
const wutils = require('../../util/wallet');
|
||||
const primutils = require('../../util/primitives');
|
||||
|
||||
const layout = {
|
||||
wdb: {
|
||||
|
|
@ -282,7 +283,7 @@ async function fundThree(wallet1, wallet2) {
|
|||
for (const [wallet, acct] of funds) {
|
||||
timeCounter++;
|
||||
const mtx1 = new MTX();
|
||||
mtx1.addInput(wutils.deterministicInput(txID++));
|
||||
mtx1.addInput(primutils.deterministicInput(txID++));
|
||||
mtx1.addOutput(await wallet.receiveAddress(acct), 10e6);
|
||||
mtx1.addOutput(OUT_ADDR, 1e6);
|
||||
txs.push(mtx1.toTX());
|
||||
|
|
|
|||
347
test/data/migrations/wallet-7-coinselector-gen.js
Normal file
347
test/data/migrations/wallet-7-coinselector-gen.js
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('bsert');
|
||||
const bdb = require('bdb');
|
||||
const Network = require('../../../lib/protocol/network');
|
||||
const MTX = require('../../../lib/primitives/mtx');
|
||||
const {Resource} = require('../../../lib/dns/resource');
|
||||
const WalletDB = require('../../../lib/wallet/walletdb');
|
||||
const wutils = require('../../util/wallet');
|
||||
const mutils = require('../../util/migrations');
|
||||
|
||||
const network = Network.get('regtest');
|
||||
|
||||
const layout = {
|
||||
wdb: {
|
||||
V: bdb.key('V'),
|
||||
|
||||
// W[wid] -> wallet id
|
||||
W: bdb.key('W', ['uint32'])
|
||||
},
|
||||
txdb: {
|
||||
prefix: bdb.key('t', ['uint32']),
|
||||
|
||||
// Coins
|
||||
c: bdb.key('c', ['hash256', 'uint32']),
|
||||
C: bdb.key('C', ['uint32', 'hash256', 'uint32']),
|
||||
d: bdb.key('d', ['hash256', 'uint32']),
|
||||
s: bdb.key('s', ['hash256', 'uint32']),
|
||||
|
||||
// confirmed by Value
|
||||
Sv: bdb.key('Sv', ['uint64', 'hash256', 'uint32']),
|
||||
// confirmed by account + Value
|
||||
SV: bdb.key('SV', ['uint32', 'uint64', 'hash256', 'uint32']),
|
||||
// Unconfirmed by value
|
||||
Su: bdb.key('Su', ['uint64', 'hash256', 'uint32']),
|
||||
// Unconfirmed by account + value
|
||||
SU: bdb.key('SU', ['uint32', 'uint64', 'hash256', 'uint32']),
|
||||
// by height
|
||||
Sh: bdb.key('Sh', ['uint32', 'hash256', 'uint32']),
|
||||
// by account + height
|
||||
SH: bdb.key('SH', ['uint32', 'uint32', 'hash256', 'uint32'])
|
||||
}
|
||||
};
|
||||
|
||||
/** @typedef {import('../../util/wallet').InboundTXOptions} InboundTXOptions */
|
||||
/** @typedef {import('../../util/wallet').OutputInfo} OutputInfo */
|
||||
|
||||
/*
|
||||
* Generate a wallet with coins in multiple states,
|
||||
* similar to coin selection indexes test at test/wallet-coinselection-test.js
|
||||
*/
|
||||
|
||||
const OUT_ADDR = 'rs1q2uqpaefgfjke38whrtvdzsve3478k38qcgg9ws';
|
||||
|
||||
const wallet1priv = 'rprvKE8qsHtkmUxUSPQdn2sFKFUcKyUQz9pKQhxjEWecnXg9hgJMsmJXcw'
|
||||
+ 'J77SqmHT1R6mcuNqVPzgT2EoGStsXaUN92VJKhQWUB6uZdL8gAZvez';
|
||||
const wallet2priv = 'rprvKE8qsHtkmUxUSR4jE7Lti9XV77hv7xxacAShw5MvxY6RfsAYVeB1WL'
|
||||
+ 'WtjiebDmqTruVJxmMeQUMkk61e83WDZbZidDnNPhHyQpeEwxjuSZuG';
|
||||
|
||||
(async () => {
|
||||
// we use legacy selection to ensure
|
||||
// deterministic coin selections.
|
||||
const selection = 'value';
|
||||
|
||||
const wdb = new WalletDB({
|
||||
network: network,
|
||||
memory: true
|
||||
});
|
||||
|
||||
await wdb.open();
|
||||
|
||||
const wallet1 = await wdb.create({
|
||||
id: 'wallet1',
|
||||
master: wallet1priv
|
||||
});
|
||||
|
||||
await wallet1.createAccount('alt');
|
||||
|
||||
const wallet2 = await wdb.create({
|
||||
id: 'wallet2',
|
||||
master: wallet2priv
|
||||
});
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), []);
|
||||
}
|
||||
|
||||
/** @type {OutputInfo[]} */
|
||||
const outputs = Array.from({ length: 15 }, () => {
|
||||
return {
|
||||
value: 1e6
|
||||
};
|
||||
});
|
||||
|
||||
/** @type {InboundTXOptions} */
|
||||
const createOptions = {
|
||||
createAddress: true,
|
||||
txPerOutput: true,
|
||||
deterministicInput: true
|
||||
};
|
||||
|
||||
// unconfirm -> confirm
|
||||
const w1a1txs = await wutils.createInboundTXs(wallet1, outputs, createOptions);
|
||||
const w1a2txs = await wutils.createInboundTXs(wallet1, outputs.map(v => ({
|
||||
...v,
|
||||
account: 1
|
||||
})), createOptions);
|
||||
const w2a1txs = await wutils.createInboundTXs(wallet2, outputs, createOptions);
|
||||
const alltxs = [...w1a1txs, ...w1a2txs, ...w2a1txs];
|
||||
|
||||
for (const tx of alltxs)
|
||||
await wdb.addTX(tx);
|
||||
|
||||
// confirm.
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), alltxs);
|
||||
|
||||
// new unconfirmed txs
|
||||
const w1a1txs2 = await wutils.createInboundTXs(wallet1, outputs, createOptions);
|
||||
const w1a2txs2 = await wutils.createInboundTXs(wallet1, outputs.map(v => ({
|
||||
...v,
|
||||
account: 1
|
||||
})), createOptions);
|
||||
const w2a1txs2 = await wutils.createInboundTXs(wallet2, outputs, createOptions);
|
||||
const alltxs2 = [...w1a1txs2, ...w1a2txs2, ...w2a1txs2];
|
||||
|
||||
for (const tx of alltxs2) {
|
||||
await wdb.addTX(tx);
|
||||
}
|
||||
|
||||
// 1 coinbase for each
|
||||
/** @type {OutputInfo[]} */
|
||||
const coinbase = [{
|
||||
value: 1e6,
|
||||
coinbase: true
|
||||
}];
|
||||
|
||||
const w1a1cb = await wutils.createInboundTXs(wallet1, coinbase, createOptions);
|
||||
const w1a2cb = await wutils.createInboundTXs(wallet1, [{
|
||||
...coinbase[0],
|
||||
account: 1
|
||||
}], createOptions);
|
||||
const w2a1cb = await wutils.createInboundTXs(wallet2, coinbase, createOptions);
|
||||
const allcb = [...w1a1cb, ...w1a2cb, ...w2a1cb];
|
||||
|
||||
for (const tx of allcb)
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), [tx]);
|
||||
|
||||
// send some coins
|
||||
{
|
||||
const sendOpts = {
|
||||
outputs: [{
|
||||
address: OUT_ADDR,
|
||||
value: 1e6
|
||||
}],
|
||||
selection
|
||||
};
|
||||
|
||||
const confirmSend1 = await wallet1.send(sendOpts);
|
||||
const confirmSend2 = await wallet1.send({ account: 1, ...sendOpts });
|
||||
const confirmSend3 = await wallet2.send(sendOpts);
|
||||
|
||||
await wallet1.send(sendOpts);
|
||||
await wallet1.send({ account: 1, ...sendOpts });
|
||||
await wallet2.send(sendOpts);
|
||||
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), [
|
||||
confirmSend1, confirmSend2, confirmSend3
|
||||
]);
|
||||
}
|
||||
|
||||
// unconfirmed
|
||||
{
|
||||
const sendOpts = {
|
||||
depth: 2,
|
||||
outputs: [{
|
||||
address: OUT_ADDR,
|
||||
value: 1e6
|
||||
}],
|
||||
selection
|
||||
};
|
||||
|
||||
const mtx1 = await wallet1.createTX({ account: 0, ...sendOpts });
|
||||
const mtx2 = await wallet1.createTX({ account: 1, ...sendOpts });
|
||||
const mtx3 = await wallet2.createTX(sendOpts);
|
||||
|
||||
const txs = [mtx1, mtx2, mtx3].map(mtx => mtx.toTX());
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), txs);
|
||||
}
|
||||
|
||||
{
|
||||
// double spend
|
||||
const sendOpts = {
|
||||
depth: 1,
|
||||
outputs: [{
|
||||
address: OUT_ADDR,
|
||||
value: 1e6
|
||||
}],
|
||||
selection
|
||||
};
|
||||
|
||||
const mtx1 = await wallet1.createTX({ account: 0, ...sendOpts });
|
||||
const mtx2 = await wallet1.createTX({ account: 1, ...sendOpts });
|
||||
const mtx3 = await wallet2.createTX(sendOpts);
|
||||
|
||||
const txs = [mtx1, mtx2, mtx3].map(mtx => mtx.toTX());
|
||||
const entry = wutils.nextEntry(wdb);
|
||||
await wdb.addBlock(entry, txs);
|
||||
|
||||
const discedTXCount = await wdb.removeBlock(entry);
|
||||
assert(discedTXCount === txs.length);
|
||||
|
||||
const coins1 = mtx1.inputs.map(input => mtx1.view.getCoinFor(input));
|
||||
const coins2 = mtx2.inputs.map(input => mtx2.view.getCoinFor(input));
|
||||
const coins3 = mtx3.inputs.map(input => mtx3.view.getCoinFor(input));
|
||||
|
||||
const dblspend1 = new MTX();
|
||||
dblspend1.addOutput({
|
||||
address: OUT_ADDR,
|
||||
value: 1e6
|
||||
});
|
||||
|
||||
for (const coin of coins1)
|
||||
dblspend1.addCoin(coin);
|
||||
|
||||
await wallet1.sign(dblspend1);
|
||||
dblspend1.check();
|
||||
|
||||
const dblspend2 = new MTX();
|
||||
dblspend2.addOutput({
|
||||
address: OUT_ADDR,
|
||||
value: 1e6
|
||||
});
|
||||
|
||||
for (const coin of coins2)
|
||||
dblspend2.addCoin(coin);
|
||||
|
||||
await wallet1.sign(dblspend2);
|
||||
dblspend2.check();
|
||||
|
||||
const dblspend3 = new MTX();
|
||||
dblspend3.addOutput({
|
||||
address: OUT_ADDR,
|
||||
value: 1e6
|
||||
});
|
||||
|
||||
for (const coin of coins3)
|
||||
dblspend3.addCoin(coin);
|
||||
|
||||
await wallet2.sign(dblspend3);
|
||||
dblspend3.check();
|
||||
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), [
|
||||
dblspend1.toTX(),
|
||||
dblspend2.toTX(),
|
||||
dblspend3.toTX()
|
||||
]);
|
||||
}
|
||||
|
||||
// do some non spendables as well.
|
||||
{
|
||||
const sendOpts = { selection };
|
||||
|
||||
const availableName = 'testname-1';
|
||||
await wallet1.importName(availableName);
|
||||
await wallet2.importName(availableName);
|
||||
|
||||
const openTX = await wallet1.sendOpen(availableName, sendOpts);
|
||||
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), [openTX]);
|
||||
|
||||
for (let i = 0; i < network.names.treeInterval + 1; i++)
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), []);
|
||||
|
||||
const bid1 = await wallet1.sendBid(availableName, 1e4, 1e4, sendOpts);
|
||||
const bid2 = await wallet2.sendBid(availableName, 2e4, 2e5, sendOpts);
|
||||
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), [bid1, bid2]);
|
||||
for (let i = 0; i < network.names.biddingPeriod; i++)
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), []);
|
||||
|
||||
const reveal1 = await wallet1.sendReveal(availableName, sendOpts);
|
||||
const reveal2 = await wallet2.sendReveal(availableName, sendOpts);
|
||||
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), [reveal1, reveal2]);
|
||||
|
||||
for (let i = 0; i < network.names.revealPeriod; i++)
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), []);
|
||||
|
||||
// Don't send this one, have it locked
|
||||
// const redeem1 = await wallet1.sendRedeem(availableName, sendOpts);
|
||||
const resource = Resource.fromJSON({records: []});
|
||||
const register = await wallet2.sendUpdate(availableName, resource, sendOpts);
|
||||
|
||||
await wdb.addBlock(wutils.nextEntry(wdb), [register]);
|
||||
}
|
||||
|
||||
const {
|
||||
dump,
|
||||
prefixes
|
||||
} = await getMigrationDump(wdb);
|
||||
console.log(JSON.stringify({
|
||||
prefixes,
|
||||
data: dump
|
||||
}, null, 2));
|
||||
|
||||
await wdb.close();
|
||||
})().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {WalletDB} wdb
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
async function getMigrationDump(wdb) {
|
||||
const prefixes = [
|
||||
layout.wdb.V.encode().toString('hex')
|
||||
];
|
||||
|
||||
for (let i = 1; i < 3; i++) {
|
||||
prefixes.push(layout.wdb.W.encode(i).toString('hex'));
|
||||
}
|
||||
|
||||
for (let i = 1; i < 3; i++) {
|
||||
const tprefix = layout.txdb.prefix.encode(i).toString('hex');
|
||||
|
||||
for (const key of Object.keys(layout.txdb)) {
|
||||
if (key === 'prefix')
|
||||
continue;
|
||||
|
||||
const val = layout.txdb[key];
|
||||
|
||||
assert(val.id.toString('hex') === mutils.prefix2hex(key));
|
||||
const prefix = mutils.prefix2hex(key);
|
||||
prefixes.push(tprefix + prefix);
|
||||
}
|
||||
}
|
||||
|
||||
const dump = await wutils.dumpWDB(wdb, prefixes);
|
||||
|
||||
return {
|
||||
dump,
|
||||
prefixes
|
||||
};
|
||||
}
|
||||
956
test/data/migrations/wallet-7-coinselector.json
Normal file
956
test/data/migrations/wallet-7-coinselector.json
Normal file
|
|
@ -0,0 +1,956 @@
|
|||
{
|
||||
"description": "Migration for coin selection in the wallet",
|
||||
"prefixes": [
|
||||
"56",
|
||||
"5700000001",
|
||||
"5700000002",
|
||||
"740000000163",
|
||||
"740000000143",
|
||||
"740000000164",
|
||||
"740000000173",
|
||||
"740000000153",
|
||||
"740000000153",
|
||||
"740000000153",
|
||||
"740000000153",
|
||||
"740000000153",
|
||||
"740000000153",
|
||||
"740000000263",
|
||||
"740000000243",
|
||||
"740000000264",
|
||||
"740000000273",
|
||||
"740000000253",
|
||||
"740000000253",
|
||||
"740000000253",
|
||||
"740000000253",
|
||||
"740000000253",
|
||||
"740000000253"
|
||||
],
|
||||
"before": {
|
||||
"56": "77616c6c657404000000",
|
||||
"5700000001": "0777616c6c657431",
|
||||
"5700000002": "0777616c6c657432",
|
||||
"740000000143000000000a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "00",
|
||||
"740000000143000000000c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000": "00",
|
||||
"7400000001430000000017b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000": "00",
|
||||
"74000000014300000000240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000": "00",
|
||||
"7400000001430000000026ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000": "00",
|
||||
"7400000001430000000028dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "00",
|
||||
"740000000143000000003896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "00",
|
||||
"740000000143000000003dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000": "00",
|
||||
"740000000143000000005ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000001": "00",
|
||||
"740000000143000000006b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000": "00",
|
||||
"740000000143000000006e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000": "00",
|
||||
"740000000143000000007284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000": "00",
|
||||
"740000000143000000007541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000": "00",
|
||||
"7400000001430000000099fe9437e4242bb7c6107f5b9131f807ead2ad0c70dfe039b34d1f801a51bc0500000000": "00",
|
||||
"740000000143000000009c98d2db8df0a5d4b5b1ee3074f872541b4c9fccd5e13256915ff22049faaf1d00000000": "00",
|
||||
"74000000014300000000ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "00",
|
||||
"74000000014300000000ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000001": "00",
|
||||
"74000000014300000000b88b14dfc52612e8e1b84550d5ab7136d591302185938a7c59b54077c5a723f000000000": "00",
|
||||
"74000000014300000000c86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000": "00",
|
||||
"74000000014300000000cf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000": "00",
|
||||
"74000000014300000000d52fb57ff1d3a4e5e2f923421015e2b5ce392f8031dfcdbbf6e3d425fef19e5a00000000": "00",
|
||||
"74000000014300000000d5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000": "00",
|
||||
"74000000014300000000d9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000": "00",
|
||||
"74000000014300000000de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "00",
|
||||
"74000000014300000000e63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000": "00",
|
||||
"74000000014300000000eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000000": "00",
|
||||
"74000000014300000000eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "00",
|
||||
"74000000014300000000eb9dc1183d33bbb09bf6daa19e2a9c485fa5dff57386872a020f01e1fa14bf3c00000000": "00",
|
||||
"74000000014300000000ed9f7353e70f6b38e26b0433245dde90b348ac1996f50d10c827a29c5240c24100000000": "00",
|
||||
"74000000014300000000ee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000": "00",
|
||||
"74000000014300000000fa5c2be8b0a338c4bd7200c9a6cfcdc2657f80acfa9dbef5a39e0e36a60720cf00000000": "00",
|
||||
"7400000001430000000110fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000": "00",
|
||||
"740000000143000000011e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000": "00",
|
||||
"7400000001430000000124ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "00",
|
||||
"7400000001430000000134d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "00",
|
||||
"7400000001430000000135631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000": "00",
|
||||
"740000000143000000013f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000": "00",
|
||||
"740000000143000000014341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "00",
|
||||
"740000000143000000014d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "00",
|
||||
"740000000143000000014f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "00",
|
||||
"740000000143000000015491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000": "00",
|
||||
"740000000143000000015e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000": "00",
|
||||
"74000000014300000001711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000": "00",
|
||||
"7400000001430000000176cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000": "00",
|
||||
"740000000143000000018bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000": "00",
|
||||
"74000000014300000001943082f6babcfc5a5799956e8149574ab83cd8f474c6d2958fd8a78ed5b5e73d00000000": "00",
|
||||
"74000000014300000001996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000": "00",
|
||||
"74000000014300000001aa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000": "00",
|
||||
"74000000014300000001acdda30e345a698a43e7d105719c9bbae846351caa6e59c5234cc1eb4840e6a300000000": "00",
|
||||
"74000000014300000001b4bec8d2a53d512fe9c55d5af3c9565302e233cb8dab73b937791abb9cc41faf00000000": "00",
|
||||
"74000000014300000001b6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000": "00",
|
||||
"74000000014300000001bdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000": "00",
|
||||
"74000000014300000001da74c9e20a2fef35b4363a266dda1141a65d1c79d2d59ab85a1dd201731b3f8400000000": "00",
|
||||
"74000000014300000001e66914b1f93cf6d3317090f01c11aa2614f2c61fca5e22bfbd0192771222676900000000": "00",
|
||||
"74000000014300000001e7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000": "00",
|
||||
"74000000014300000001f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "00",
|
||||
"74000000014300000001f74e991d682a2b3b97e69bfebff3e7a446b528f44db2f59bf508fa206808c13800000000": "00",
|
||||
"74000000014300000001f75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000": "00",
|
||||
"7400000001630a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "000000003700000028320f00000000000014a62c71694f8bf7583bfe73bd1b2235b81ee125580000000001",
|
||||
"7400000001630c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000": "00000000ffffffff40420f00000000000014cc0fdce38328d8df1f900a638544b15f9d81f5520000000000",
|
||||
"74000000016310fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000": "00000000ffffffff40420f000000000000143ddd945091f3ec8dc5e0c0cefce32875e5037aeb0000000000",
|
||||
"74000000016317b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000": "00000000ffffffff40420f00000000000014c53ec93147b4fa7e1e1bf56f0edbdd319895e2530000000000",
|
||||
"7400000001631e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000": "00000000ffffffff40420f0000000000001427595b8603244dc6dd42d5a89d6e77a18f18e23b0000000000",
|
||||
"740000000163240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000": "00000000ffffffff40420f00000000000014e4a65896f371f330fd6aa119e933fc346cb1223b0000000000",
|
||||
"74000000016324ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "000000003700000028320f00000000000014f0f269d0cb5eb1e065b1084d1d787a7ec5b6ed8c0000000001",
|
||||
"74000000016326ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000": "00000000ffffffff40420f0000000000001401ef877cb2f639d44cb257bb2eb983d408c8893c0000000000",
|
||||
"74000000016328dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "00000000ffffffff28320f000000000000141fcdaa5bd5efa8811106f6390e5ecc327083426b0000000001",
|
||||
"74000000016334d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "000000003300000040420f000000000000145cb324e109b920b19494549b3e540f57d43c4ef10000000100",
|
||||
"74000000016335631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000": "00000000ffffffff40420f00000000000014ad37f7abb46c43e2ac8238c1c9e4a98841b3fa6c0000000000",
|
||||
"7400000001633896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "000000003300000040420f00000000000014b9e7138dd8a5fdd11fee499ccb7a6cd131edbaa30000000100",
|
||||
"7400000001633dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000": "00000000ffffffff40420f00000000000014198a5f555dc8f78bcc35bf23baefc03c954e70530000000000",
|
||||
"7400000001633f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000": "00000000ffffffff40420f00000000000014a2affbcd20614676abcac636b5db306e964411210000000000",
|
||||
"7400000001634341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "000000003300000040420f000000000000142667d9e317cf8f0aac2543fd27f38e0ab9da3ecb0000000100",
|
||||
"7400000001634d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "000000003300000040420f00000000000014d20cf3c6172ea38a0ec42514435978a76a99b1eb0000000100",
|
||||
"7400000001634f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "000000003800000028320f00000000000014c674ba19506d9e2c178ab0eb4acd47b7011927050000000001",
|
||||
"7400000001635491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000": "00000000ffffffff40420f000000000000140ca598cff494738bfbfe1b06f416bce1def2dfae0000000000",
|
||||
"7400000001635e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000": "00000000ffffffff40420f00000000000014045216fd9b9f0aeff2be3c18327c3083fc4ffd4a0000000000",
|
||||
"7400000001635ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000001": "0000000041000000d8090f0000000000001460976bb97754c7ab48bac4c894d27a4126bc23d10000000001",
|
||||
"7400000001636b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000": "00000000ffffffff40420f00000000000014433b4a5bdd977ac6ab958e07834a08fbd2e7f9610000000000",
|
||||
"7400000001636e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000": "00000000ffffffff40420f00000000000014edda79fa7ce1811ea0c0a9a2ffca881c509269020000000000",
|
||||
"740000000163711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000": "00000000ffffffff40420f000000000000144eca2ab57c2a64d356a40fab260140a48fb39ca40000000000",
|
||||
"7400000001637284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000": "00000000ffffffff40420f000000000000144725f4cf8e0fbeb8d220c54f3e38d6d1f73cba1a0000000000",
|
||||
"7400000001637541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000": "00000000ffffffff40420f000000000000142e79df92c411266e738b4488a54686c6fe0d27b80000000000",
|
||||
"74000000016376cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000": "00000000ffffffff40420f0000000000001417e6329645e6f871f3b2f01b2e8115ffc01fa0dd0000000000",
|
||||
"7400000001638bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000": "00000000ffffffff40420f000000000000149dfcfcd6ce44031193a6960f878460c3f1157b0b0000000000",
|
||||
"740000000163943082f6babcfc5a5799956e8149574ab83cd8f474c6d2958fd8a78ed5b5e73d00000000": "000000003300000040420f00000000000014646f81d8c9569f04c93d1559a606da8185cb49c10000000000",
|
||||
"740000000163996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000": "00000000ffffffff40420f0000000000001431bbc5580d28765f2172cd537369614a87c99b4d0000000000",
|
||||
"74000000016399fe9437e4242bb7c6107f5b9131f807ead2ad0c70dfe039b34d1f801a51bc0500000000": "000000003300000040420f00000000000014cc2953e1188c162d1689206ce79fabdd54c0a5750000000000",
|
||||
"7400000001639c98d2db8df0a5d4b5b1ee3074f872541b4c9fccd5e13256915ff22049faaf1d00000000": "000000003300000040420f000000000000147220144f51a799765a1cfebc74a6ff789e0cf0980000000000",
|
||||
"740000000163aa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000": "00000000ffffffff40420f000000000000141860e07aa099551e6f6d4794de9eb48aac172a450000000000",
|
||||
"740000000163ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "000000003a000000000000000000000000144a476eeca3f6b9f47b7ac6ea4953f5aec201fda002032075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d04000000000a746573746e616d652d31000001",
|
||||
"740000000163ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000001": "000000003a0000007c330f00000000000014a7cd0f0c61bcdb86967f1b11128b7d3ba7d78ec70000000001",
|
||||
"740000000163acdda30e345a698a43e7d105719c9bbae846351caa6e59c5234cc1eb4840e6a300000000": "000000003500000040420f00000000000014359d9335692983142de60a57df5a0197fd13ab760000010000",
|
||||
"740000000163b4bec8d2a53d512fe9c55d5af3c9565302e233cb8dab73b937791abb9cc41faf00000000": "000000003300000040420f000000000000147db1e3611d2409939fb385976712f032de7629ef0000000000",
|
||||
"740000000163b6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000": "00000000ffffffff40420f000000000000141b5a09ead7ea9f4c460d1a248d6980a98186f3c70000000000",
|
||||
"740000000163b88b14dfc52612e8e1b84550d5ab7136d591302185938a7c59b54077c5a723f000000000": "000000003400000040420f00000000000014332479f977c3cd9a6a915a25e7ca4e95bbfb92c00000010000",
|
||||
"740000000163bdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000": "00000000ffffffff40420f0000000000001434079c84c47cf7b4ce95b38b8b19040043b5a58b0000000000",
|
||||
"740000000163c86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000": "00000000ffffffff40420f000000000000149df7d0326747749b602967421b892f1efe19fd730000000000",
|
||||
"740000000163cf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000": "00000000ffffffff40420f000000000000147f0a0bb9e20f94b934fb814e4938ef3a783e6f970000000000",
|
||||
"740000000163d52fb57ff1d3a4e5e2f923421015e2b5ce392f8031dfcdbbf6e3d425fef19e5a00000000": "000000003300000040420f0000000000001441136683ea0485bbef46e9d9f6a2ff766128efad0000000000",
|
||||
"740000000163d5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000": "00000000ffffffff40420f0000000000001468a14e77be363ef2ee92406d5ce969a1785110130000000000",
|
||||
"740000000163d9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000": "00000000ffffffff40420f00000000000014a37f94f98c1e2cb4714d5ce5465f9d7a82f3e36e0000000000",
|
||||
"740000000163da74c9e20a2fef35b4363a266dda1141a65d1c79d2d59ab85a1dd201731b3f8400000000": "000000003300000040420f00000000000014452b000bd69cd9c4a99322d1c41e972cdcd662fe0000000000",
|
||||
"740000000163de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "000000003800000028320f00000000000014222740511246fe2b10957049ae10e0bbe7fa996c0000000001",
|
||||
"740000000163e63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000": "00000000ffffffff40420f00000000000014bfc1d60d296b8a15c489d32d5647f21e8c6cb8950000000000",
|
||||
"740000000163e66914b1f93cf6d3317090f01c11aa2614f2c61fca5e22bfbd0192771222676900000000": "000000003300000040420f000000000000142cd7a7b49b1013603d53b6a234493ee1208471750000000000",
|
||||
"740000000163e7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000": "00000000ffffffff40420f00000000000014f59d25508e19abeec49c3377bedd48ad7947af440000000000",
|
||||
"740000000163eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000000": "0000000047000000102700000000000000142315e1a3850c7afd6f1be87df18dea1d3939d0a904032075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a00000020c616d48ae4039aad2438bc47a11e0ec0d883f5dfc1f34aa10944085c57d79a13000001",
|
||||
"740000000163eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "00000000470000009c2c0f000000000000147a98b6621e4ccc528560645f6a7682633a24fad80000000001",
|
||||
"740000000163eb9dc1183d33bbb09bf6daa19e2a9c485fa5dff57386872a020f01e1fa14bf3c00000000": "000000003300000040420f000000000000146a94c2244bd9a4b5257a8c946b68af72f4118b350000000000",
|
||||
"740000000163ed9f7353e70f6b38e26b0433245dde90b348ac1996f50d10c827a29c5240c24100000000": "000000003300000040420f000000000000142f6d31b494bdfa42034719ca28894496cdb893590000000000",
|
||||
"740000000163ee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000": "00000000ffffffff40420f000000000000147e7a6df077a7d3a39fba31246574634375439e3c0000000000",
|
||||
"740000000163f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "00000000ffffffff28320f000000000000145258eb6f21709d00f66fa39deab6e8d5391eae5f0000000001",
|
||||
"740000000163f74e991d682a2b3b97e69bfebff3e7a446b528f44db2f59bf508fa206808c13800000000": "000000003300000040420f00000000000014cfe3810f02a4af8dd98092bb6650841bac0ff2c40000000000",
|
||||
"740000000163f75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000": "00000000ffffffff40420f000000000000144931bd2996fb46b2f17a5f4bae7d0b2a04ee65bc0000000000",
|
||||
"740000000163fa5c2be8b0a338c4bd7200c9a6cfcdc2657f80acfa9dbef5a39e0e36a60720cf00000000": "000000003300000040420f000000000000144c43158dc848068c85406c14957b0cab073f343b0000000000",
|
||||
"7400000001640a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "000000003300000040420f000000000000144f9644e04b6bbc4f53ae49e444d005524cc1422f000000",
|
||||
"7400000001640a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000001": "000000003300000040420f000000000000140441cbe166229b9e2adb6aa90751052523cb298d000000",
|
||||
"74000000016424ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "000000003300000040420f00000000000014f4f1b2cd73eebe34bef87c174e5d4903e86493ff000000",
|
||||
"74000000016424ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000001": "000000003300000040420f000000000000146337e3ecd9354ba7a5fbe0ce2feb6d3bc5558a54000000",
|
||||
"74000000016428dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "000000003300000040420f000000000000145cb324e109b920b19494549b3e540f57d43c4ef1000000",
|
||||
"74000000016428dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000001": "000000003300000040420f00000000000014b9e7138dd8a5fdd11fee499ccb7a6cd131edbaa3000000",
|
||||
"740000000164320e6e6cffd8d9b904d97f904536c6c410f47d7ed856e354b0836b6bd47ec44b00000000": "000000003300000040420f000000000000145428d3cebe215a9f78bf3ca6d3ebc055b828f3d9000000",
|
||||
"740000000164320e6e6cffd8d9b904d97f904536c6c410f47d7ed856e354b0836b6bd47ec44b00000001": "000000003300000040420f000000000000146dddb81cee99d34e617876a2d4bd091fea7da844000000",
|
||||
"7400000001644f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "000000003300000040420f00000000000014f7c2c1cf022472febc2deb36c91ae2e450c36b29000000",
|
||||
"7400000001644f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000001": "000000003300000040420f00000000000014309a0821ae883849b41aeb44c24b075af338b8c0000000",
|
||||
"7400000001645ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000000": "000000003300000040420f000000000000142a52b414589aca0f9e6b79be9563e3e0b8e053aa000000",
|
||||
"74000000016499e639a2ab769f77f51c3752ca4d85436abd3d6ee0fa28d249fc67a7f4e8422200000000": "000000003300000040420f00000000000014e154c1b406841cc45d1d63fbd0c36003b5e34cc9000000",
|
||||
"74000000016499e639a2ab769f77f51c3752ca4d85436abd3d6ee0fa28d249fc67a7f4e8422200000001": "000000003300000040420f000000000000149d03a12b2facd319be3b174ac49e78effae0f9fc000000",
|
||||
"740000000164ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "000000003300000040420f00000000000014b27ecd0a361b0fe9acafecdc47841e021a4ebf50000000",
|
||||
"740000000164de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "000000003300000040420f0000000000001499b6d711eb6ba45313ed8ff37d9a8ec5df572750000000",
|
||||
"740000000164de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000001": "000000003300000040420f000000000000149259252916ee5b5573f1f911ed49c99592b1db44000000",
|
||||
"740000000164eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000000": "0000000041000000102700000000000000142315e1a3850c7afd6f1be87df18dea1d3939d0a903042075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a0000000a746573746e616d652d3120d239a4dc0ce73cc36bf38204e3b0a438d353a7c6d57889fa6e88fff91265145500",
|
||||
"740000000164eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "000000003300000040420f000000000000148d375a0132f2077de9c4a96c66e15c24c5501851000000",
|
||||
"740000000164f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "000000003300000040420f000000000000142667d9e317cf8f0aac2543fd27f38e0ab9da3ecb000000",
|
||||
"740000000164f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000001": "000000003300000040420f00000000000014d20cf3c6172ea38a0ec42514435978a76a99b1eb000000",
|
||||
"74000000017303681f8ee2b9bd28df7af7445a589052965b60c4d89d13269b483aafd0ea9db600000000": "6b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000",
|
||||
"7400000001730fff2e108fcc22c30f2e5117182fc26f74cd2a79c3372d196e7eff08416276d500000000": "0a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000",
|
||||
"74000000017310576dd1a93f478542464d6721bc6053cdd310f68241cf7f94864edc80d8f23b00000000": "bdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000",
|
||||
"74000000017313642f3ae0597fae5368a9ab3aa5002159ee421b622fff2dfaf4431259e117fb00000000": "0a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546401000000",
|
||||
"7400000001731372f66217b7c16d46080b70af90951addb9fdf9a148566208957b82b5b1771200000000": "24ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000",
|
||||
"7400000001731d14f5deb4d7480fe6568ede44151397fc4fa3da9ed32b6c405ca946d12e937200000000": "1e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000",
|
||||
"7400000001732858e5aedda20aa45293fa3aa64722c13ea3d18adc8e952728739e450ef2023b00000000": "b6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000",
|
||||
"7400000001732e1c6abb0f6ed5b4fcc53b6ebccaf5f155e0fa7d763a12a31ae68ac9408d02fd00000000": "26ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000",
|
||||
"740000000173323b9dd49fb66e770de1fdd46bb57028a778e140de37a7ca5596a57100defbf900000000": "24ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59901000000",
|
||||
"74000000017334d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "28dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000",
|
||||
"74000000017336b9062567c977a2b897f8cbd547ce78fa3bd598ef1c6b5499e71229514fe3ed00000000": "e63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000",
|
||||
"74000000017337066d4d4d245fc5bc01fe8c55005c9b92d7a9f8deb443a9369d4423acd9570000000000": "ee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000",
|
||||
"7400000001733896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "28dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7701000000",
|
||||
"7400000001733a3126fbbf206d75ecfbb40d5da8f39068263a6dbaa4e047529fd3ddb11fd8b800000000": "de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000",
|
||||
"7400000001733ad9ed2ba729a6419907e522c55e246fa70eebaae3a01a3794b2df42fc36efb500000000": "7284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000",
|
||||
"74000000017340660cb50912d86a4ec527cd5af973233d7f9bdee7361bf7fadfdf1243bfe43800000000": "240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000",
|
||||
"740000000173408c946546253b8ba7642a80c1089bfe1a0fa76c33627d6f2d3684ee17b55ebe00000000": "de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d401000000",
|
||||
"740000000173425ac3ec38bb16ae168a5fc25a8702bc65a6d9cd09f6896e1dc28499e2b18da800000000": "3dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000",
|
||||
"7400000001734341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000",
|
||||
"7400000001734561925263b2563548dd72b116034abee46210e88371f763563b6cd33eb0053500000000": "99e639a2ab769f77f51c3752ca4d85436abd3d6ee0fa28d249fc67a7f4e8422200000000",
|
||||
"7400000001734d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf01000000",
|
||||
"7400000001734dfc35d2d60a286f00bcff7fe5a868279ca21ec1903c1ef089facae0131bcbb600000000": "99e639a2ab769f77f51c3752ca4d85436abd3d6ee0fa28d249fc67a7f4e8422201000000",
|
||||
"740000000173515b89fe7e7cd16dea4e1981b4f75e0a02b896ad4d30d2200daf82d8956ba8af00000000": "10fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000",
|
||||
"74000000017353cb0828e3d769a6c997319c775e387c41280157d65fda2c9b997af84d7f90db00000000": "d9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000",
|
||||
"7400000001735442c34bd79edaa96143c2fb37fa65fd304c65fc65e154cfc67468645c402f1b00000000": "35631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000",
|
||||
"74000000017354f473602bcde9d4f9f719fd4e3edabf323f5a3593adb409e49c10d549ce834800000000": "4f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000",
|
||||
"7400000001735991ca040bea6b28ed9f7b388ecd3e8b5afe19fec959bda94c6ea523aef0b30000000000": "3f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000",
|
||||
"7400000001735ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000000": "eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000000",
|
||||
"74000000017362ffbfd09fd83be044075929214ab559e3b9994197a792cc60b48ad80caff85700000000": "ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000",
|
||||
"7400000001736b96d4992c1901179bcff23c0cb649ae98f6b878ab8961341d359944494874d000000000": "aa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000",
|
||||
"7400000001736cdc4bd0ac996647c5e8b59a7f1616ce613ce3b4a9725a62a3abf4c7c43d114f00000000": "5ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000000",
|
||||
"7400000001736dda8d21c1c543e52fa2201dd186dda5bce740cbe979e31d8c92bf6936e68bfa00000000": "996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000",
|
||||
"74000000017377404f33697b378fb347d8d1fc6d341468f19cf2aaf7a2daa21679e0e5b4c5df00000000": "e7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000",
|
||||
"7400000001737858cecbeefa2985c7ff7a126143e1099f6c2a1e6bb8f8b49876284c5aef11b500000000": "4f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a01000000",
|
||||
"7400000001737a669bbd9b6071db8e6032adfdc266ece43a0375cbdda6e77925b855e2f066e000000000": "320e6e6cffd8d9b904d97f904536c6c410f47d7ed856e354b0836b6bd47ec44b00000000",
|
||||
"7400000001737e905920371ec5f99176f13c5d5010758493c830fe520e97cc4f9d66d80b3efe00000000": "c86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000",
|
||||
"7400000001737f161a9bd7c781ec7c25e094a012bd1931dec18faa2eaa5fd56b20ee19b1447100000000": "6e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000",
|
||||
"740000000173807957571a3add9343a9d932cb90955446f9797c984c8582176e5d8e5790f12600000000": "eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed01000000",
|
||||
"7400000001738486b24023f38c8f595dcb48880e9673e1e0434d601f1e8871ed60a8acda154d00000000": "f75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000",
|
||||
"74000000017389220598e0a127f887cf16b37c62f26d9cace85a448243522f9e41b408ade26400000000": "17b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000",
|
||||
"7400000001738dbb3eb02c8f8cf6cf03346a72d733d4ca9b7e7e5dacc1afeddfb5697376c30800000000": "0c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000",
|
||||
"7400000001738eff95c81bcf3d5467c32ce9abf81872481c568c9f90947580122e8fe177dc7000000000": "320e6e6cffd8d9b904d97f904536c6c410f47d7ed856e354b0836b6bd47ec44b01000000",
|
||||
"740000000173a2ba1d077eded3608cdf54c41c514c59b02aefe28af3e4e602f460626be42d3e00000000": "76cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000",
|
||||
"740000000173a854a5b169d381f72109048ba56b41dd547ba2ac6b0b838d60d675fdcdf5310700000000": "cf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000",
|
||||
"740000000173b352f75f0642ce5589167154fe760a84fb047418a871d5225fd1d66c53c4ebd100000000": "711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000",
|
||||
"740000000173b37353c0edbb420e054eff5a0f05b3e0e99e852393f449c854057527a312fc3c00000000": "5491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000",
|
||||
"740000000173b609f6515a3dcdf32b896678a89b1d867f1bd93b7485f82d1e901c51e875b2c200000000": "d5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000",
|
||||
"740000000173e8634b9bc9031ba1617a827f1d51785c25bf5164692257ccbbfd3c5d2918c30d00000000": "8bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000",
|
||||
"740000000173e899425912717de079a89b958274948c7056308ae920c899666e0cf3cfc0ffa100000000": "7541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000",
|
||||
"740000000173ecf456658d108c368e6bb7473305a5278cae99cc199c55bfeb618420d6a6423100000000": "5e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000",
|
||||
"740000000243000000001ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000": "00",
|
||||
"740000000243000000001cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000": "00",
|
||||
"740000000243000000001e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000": "00",
|
||||
"740000000243000000002ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "00",
|
||||
"740000000243000000003cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000": "00",
|
||||
"74000000024300000000458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "00",
|
||||
"740000000243000000004eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000": "00",
|
||||
"7400000002430000000053956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000": "00",
|
||||
"74000000024300000000554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "00",
|
||||
"740000000243000000005dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000001": "00",
|
||||
"7400000002430000000063864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000001": "00",
|
||||
"74000000024300000000683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "00",
|
||||
"7400000002430000000086387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000": "00",
|
||||
"740000000243000000008b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000": "00",
|
||||
"740000000243000000008eba67a23de92df548ecbb4f40be648bbf870a1de67191ad0b576c1fab59ea8b00000000": "00",
|
||||
"74000000024300000000952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "00",
|
||||
"74000000024300000000a7d8e41fbc89aacc349effd58b140f08b84ab4d33775bb967a678e8bf4d8c00a00000000": "00",
|
||||
"74000000024300000000ad6c09f1d7c9eacd449af12280c6ac397061a27bed995bc5dfe7b24ce2c49e3a00000000": "00",
|
||||
"74000000024300000000b37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000": "00",
|
||||
"74000000024300000000bf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000": "00",
|
||||
"74000000024300000000d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000000": "00",
|
||||
"74000000024300000000d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000001": "00",
|
||||
"74000000024300000000d949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000": "00",
|
||||
"74000000024300000000db3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000": "00",
|
||||
"74000000024300000000db40da9ef39f8b52f0cfc84fe1bd4320f9dcab3f0c7425af1e505cfd59ecedbb00000000": "00",
|
||||
"74000000024300000000ddef667255e254fc9d885ee04fe4852dba08d4b7ba84e86562afe6351d62098000000000": "00",
|
||||
"74000000024300000000e674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000": "00",
|
||||
"74000000024300000000ee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000": "00",
|
||||
"74000000024300000000ee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000": "00",
|
||||
"74000000024300000000fa72230236318c48e79926683a93baa4ccf09e856a78dfef9e1bf20f9b947dbe00000000": "00",
|
||||
"74000000024300000000fc7c46c4caa0d66e55e2b70c82fe65a68fef5fabfb5c903cc264afc68ea6b7bd00000000": "00",
|
||||
"7400000002631ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000": "00000000ffffffff40420f00000000000014ac46b87e7e6708a4f2b7e278b78ae7ba02c04b3b0000000000",
|
||||
"7400000002631cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000": "00000000ffffffff40420f00000000000014975caef4f934d0494c828c38140a139019dc71540000000000",
|
||||
"7400000002631e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000": "00000000ffffffff40420f00000000000014f4a5f44033305254c96ee91dd253f903af5944aa0000000000",
|
||||
"7400000002632ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "000000003800000028320f00000000000014fb392be3e9bd032482a3b24ec077f27c4fcf00b20000000001",
|
||||
"7400000002633cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000": "00000000ffffffff40420f000000000000145493e23f073a3dba4442e529038ffd00c3cb72700000000000",
|
||||
"740000000263458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "000000003300000040420f00000000000014bd835f1fd372fcc462a59b7e088dcaf1bdc8ca540000000100",
|
||||
"7400000002634eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000": "00000000ffffffff40420f00000000000014a956453db168abea91324667b05b286fa5c03bd50000000000",
|
||||
"74000000026353956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000": "00000000ffffffff40420f000000000000141ec0760b28899bb7b5ef6c617915c3db1d7324580000000000",
|
||||
"740000000263554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "000000003300000040420f00000000000014dbba0b358b90b46b04a86b01a46e0b844ea7c3680000000100",
|
||||
"7400000002635dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000001": "0000000041000000a8230c000000000000142e9187f08252e4be2044f7a41cd25ceb7ae96d460000000001",
|
||||
"74000000026363864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000001": "0000000047000000a4ae020000000000001432aa9c919d911fed1b053a452dcbf3704a759e940000000001",
|
||||
"740000000263683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "00000000ffffffff28320f00000000000014bfe57d3d51c357a99aa9c84195fe8e000273399a0000000001",
|
||||
"74000000026386387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000": "00000000ffffffff40420f00000000000014ce48fa97354847c1f35d85763e776bd9e372ec240000000000",
|
||||
"7400000002638b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000": "00000000ffffffff40420f0000000000001468a4797e126034fa8d58cdb8f709d61bda3fd7660000000000",
|
||||
"7400000002638eba67a23de92df548ecbb4f40be648bbf870a1de67191ad0b576c1fab59ea8b00000000": "000000003300000040420f00000000000014093a264c767174696332bf57a93ffceaabe23bde0000000000",
|
||||
"740000000263952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "000000003700000028320f00000000000014b44aae19323f53e63e5cd184dba19a71616808150000000001",
|
||||
"740000000263a7d8e41fbc89aacc349effd58b140f08b84ab4d33775bb967a678e8bf4d8c00a00000000": "000000003600000040420f000000000000147ed369a0353f353c38c282be4b153f1f883b04de0000010000",
|
||||
"740000000263ad6c09f1d7c9eacd449af12280c6ac397061a27bed995bc5dfe7b24ce2c49e3a00000000": "000000003300000040420f0000000000001478d8fc4cbe36b0fde3b3d337cebd4520dae1e4c60000000000",
|
||||
"740000000263b37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000": "00000000ffffffff40420f000000000000148c446d6b2942f2ed67b8e78c64a0a84cfa079f920000000000",
|
||||
"740000000263bf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000": "00000000ffffffff40420f000000000000141e3feab7c10d898c5762226c1a3d6969f470a89e0000000000",
|
||||
"740000000263d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000000": "0000000052000000102700000000000000146ff78bc8fc05796fca3c60163568ff5c9dd22acc06042075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a000000010020ae3895cf597eff05b19e02a70ceeeecb9dc72dbfe6504a50e9343a72f06a87c5000001",
|
||||
"740000000263d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000001": "00000000520000006c16000000000000001463ee2af024ea3dd2411e4de306e7f13fb26659df0000000001",
|
||||
"740000000263d949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000": "00000000ffffffff40420f0000000000001454b98d922ac0a29f3dd4a71280bea1aa35297c130000000000",
|
||||
"740000000263db3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000": "00000000ffffffff40420f00000000000014a7f6ac09b90a612149c8853772515fcc849361520000000000",
|
||||
"740000000263db40da9ef39f8b52f0cfc84fe1bd4320f9dcab3f0c7425af1e505cfd59ecedbb00000000": "000000003300000040420f000000000000143229a7234fc49dbd38ea2f203e01e4a7f15c4bd40000000000",
|
||||
"740000000263ddef667255e254fc9d885ee04fe4852dba08d4b7ba84e86562afe6351d62098000000000": "000000003300000040420f0000000000001483bd567dae4fb1abf5f6c53379e23c87ec036e770000000000",
|
||||
"740000000263e674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000": "00000000ffffffff40420f0000000000001419703fb89be3e452f3cf0541d02f6118cda5eced0000000000",
|
||||
"740000000263ee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000": "00000000ffffffff40420f000000000000142965055c105f10931d54d690ae8d5aca7aa5434d0000000000",
|
||||
"740000000263ee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000": "00000000ffffffff40420f000000000000140b2909a9cc84726cb1f01c32e45288f5b49349ad0000000000",
|
||||
"740000000263fa72230236318c48e79926683a93baa4ccf09e856a78dfef9e1bf20f9b947dbe00000000": "000000003300000040420f00000000000014784cc5364704372f14e7b68600aafbb2cd4aea390000000000",
|
||||
"740000000263fc7c46c4caa0d66e55e2b70c82fe65a68fef5fabfb5c903cc264afc68ea6b7bd00000000": "000000003300000040420f000000000000149b18d692909cfbd2b7d9342d41932ed4e5a7e7930000000000",
|
||||
"7400000002642ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "000000003300000040420f0000000000001446e641691144a03add0db507a26404313606782e000000",
|
||||
"7400000002642ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000001": "000000003300000040420f0000000000001449fec1e6fcbd2ccc2597e1b12986fc6ec8e64c07000000",
|
||||
"7400000002645dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000000": "000000003300000040420f000000000000149c1222d5dd6094fb7ee38bdb79e5d8eb7bdfd37b000000",
|
||||
"74000000026463864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000000": "0000000041000000400d03000000000000146ff78bc8fc05796fca3c60163568ff5c9dd22acc03042075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a0000000a746573746e616d652d3120909aec88e869c4d284606d5cc8054853bfa7c4e24c6041c4d8598f1b29bf282400",
|
||||
"740000000264683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "000000003300000040420f00000000000014bd835f1fd372fcc462a59b7e088dcaf1bdc8ca54000000",
|
||||
"740000000264683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000001": "000000003300000040420f00000000000014dbba0b358b90b46b04a86b01a46e0b844ea7c368000000",
|
||||
"740000000264952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "000000003300000040420f00000000000014852e98d8b8ab2946445f20b051e6b055b492c506000000",
|
||||
"740000000264952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000001": "000000003300000040420f00000000000014195fe465b1df13832ebe58e00aaefa05702ad39b000000",
|
||||
"740000000264c2d3e816222195ea0b04be6b7bb664e0b617f9c91a898682ea7214d5168c293700000000": "000000003300000040420f000000000000148e659d46d7ceb9464cc04f3aeb73bc2702c1e7a3000000",
|
||||
"740000000264c2d3e816222195ea0b04be6b7bb664e0b617f9c91a898682ea7214d5168c293700000001": "000000003300000040420f0000000000001424fd6822d0ad3e981832391cb004484cbcab15da000000",
|
||||
"740000000264d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000000": "0000000047000000204e00000000000000146ff78bc8fc05796fca3c60163568ff5c9dd22acc04032075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a000000200cc30191083dcbf8e46ecaa8972af321588068915326f7f0851b2cee9872050500",
|
||||
"7400000002730cd502099a8d25b5b574bc381cf60b84f45163ccb32cb44d64d53375a2d8a97a00000000": "4eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000",
|
||||
"7400000002731012b7e8316dcc4c65e0b80bc8b964962088c25629483bc27a81fb072726432800000000": "952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000",
|
||||
"7400000002731c820c68189da48293553b1cd7465cf7f75ac3bf2db9172f5f9ddebf3d10ad8500000000": "ee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000",
|
||||
"74000000027325cf4fe01b9170e6641cd56aa6d2110ed7755c831648f6b209010a65cd17bfc900000000": "952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e01000000",
|
||||
"7400000002732c2a543be67720878d34455f26e69f830c741b4dfbf33cfcd4998b881a9c97bd00000000": "8b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000",
|
||||
"7400000002732dd1d165fc1bfa68e3bfec4e134141df8da1e37b9daa8436109cc0fb4b1805f600000000": "ee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000",
|
||||
"740000000273458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000",
|
||||
"7400000002734e26855b7519729fbbd5d525ded78b92308ebb2acc11f595793997d358db2dc900000000": "b37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000",
|
||||
"740000000273554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331101000000",
|
||||
"7400000002735dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000000": "63864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000000",
|
||||
"7400000002735f8e95f14b4655f2cd5f476b02390b083dd4208ccf1c73bc3bf3f7ae294d728b00000000": "2ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000",
|
||||
"74000000027361886f1b56565fb2455e0b7d890eb674e01679dab480319320607e03c218912800000000": "d949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000",
|
||||
"74000000027363849657fbafa56c0be2daa74185c51ff8323d1984b3d62becafb83a9f43866c00000000": "2ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da01000000",
|
||||
"74000000027363864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000000": "d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000000",
|
||||
"74000000027365b569f91f6b28d447f12c5e093a68933f58ef7d60ab129fa236e61d406e5c8200000000": "c2d3e816222195ea0b04be6b7bb664e0b617f9c91a898682ea7214d5168c293700000000",
|
||||
"7400000002737250af6eb0c99bc0390f068687ee0f51e32340c16872da8c56427dd02c17aaa300000000": "86387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000",
|
||||
"7400000002737b1e678d97e5ac5cf768247a497c5b76b3e6e2eaf5dab87abef949e30385d19400000000": "c2d3e816222195ea0b04be6b7bb664e0b617f9c91a898682ea7214d5168c293701000000",
|
||||
"74000000027384440206ad559e1539aaf0d6b81f24c7bcca619032684024df17cb0b48a90f2d00000000": "5dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000000",
|
||||
"74000000027386416be467af312440c129867613049d435ba837f71091aa56cef7829440f18e00000000": "1e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000",
|
||||
"7400000002738a306eeb3501386cb9462e357d8574d0a51ab1a3904fcf6ad7fb516901bd90ff00000000": "bf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000",
|
||||
"7400000002738ab5e6fc1290ea699b3f2c98e99226212e6b7217a0f774e17697bf88d77fa2ff00000000": "53956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000",
|
||||
"7400000002738ef53020504301017d883fed0eed75001cb2242d219a372bb334895ef588789900000000": "1cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000",
|
||||
"740000000273d007a06dd5e690763c223421b6126ba058d04216a3a02e4c164348513b6495ea00000000": "3cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000",
|
||||
"740000000273dfc49e74ea08407f6722adf59ca4e8c782ee0183beac232e02675863fc5fbbad00000000": "e674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000",
|
||||
"740000000273f8237a38c8e06c92a717eee4301299d785dd32765027b2fe30c683721c97379e00000000": "db3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000",
|
||||
"740000000273fc12d97b516e6e7fe68e9bbcb622784543b45d5b89c00d720115d1e697eca4f500000000": "1ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000"
|
||||
},
|
||||
"after": {
|
||||
"56": "77616c6c657405000000",
|
||||
"5700000001": "0777616c6c657431",
|
||||
"5700000002": "0777616c6c657432",
|
||||
"740000000143000000000a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "00",
|
||||
"740000000143000000000c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000": "00",
|
||||
"7400000001430000000017b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000": "00",
|
||||
"74000000014300000000240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000": "00",
|
||||
"7400000001430000000026ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000": "00",
|
||||
"7400000001430000000028dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "00",
|
||||
"740000000143000000003896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "00",
|
||||
"740000000143000000003dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000": "00",
|
||||
"740000000143000000005ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000001": "00",
|
||||
"740000000143000000006b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000": "00",
|
||||
"740000000143000000006e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000": "00",
|
||||
"740000000143000000007284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000": "00",
|
||||
"740000000143000000007541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000": "00",
|
||||
"7400000001430000000099fe9437e4242bb7c6107f5b9131f807ead2ad0c70dfe039b34d1f801a51bc0500000000": "00",
|
||||
"740000000143000000009c98d2db8df0a5d4b5b1ee3074f872541b4c9fccd5e13256915ff22049faaf1d00000000": "00",
|
||||
"74000000014300000000ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "00",
|
||||
"74000000014300000000ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000001": "00",
|
||||
"74000000014300000000b88b14dfc52612e8e1b84550d5ab7136d591302185938a7c59b54077c5a723f000000000": "00",
|
||||
"74000000014300000000c86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000": "00",
|
||||
"74000000014300000000cf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000": "00",
|
||||
"74000000014300000000d52fb57ff1d3a4e5e2f923421015e2b5ce392f8031dfcdbbf6e3d425fef19e5a00000000": "00",
|
||||
"74000000014300000000d5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000": "00",
|
||||
"74000000014300000000d9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000": "00",
|
||||
"74000000014300000000de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "00",
|
||||
"74000000014300000000e63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000": "00",
|
||||
"74000000014300000000eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000000": "00",
|
||||
"74000000014300000000eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "00",
|
||||
"74000000014300000000eb9dc1183d33bbb09bf6daa19e2a9c485fa5dff57386872a020f01e1fa14bf3c00000000": "00",
|
||||
"74000000014300000000ed9f7353e70f6b38e26b0433245dde90b348ac1996f50d10c827a29c5240c24100000000": "00",
|
||||
"74000000014300000000ee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000": "00",
|
||||
"74000000014300000000fa5c2be8b0a338c4bd7200c9a6cfcdc2657f80acfa9dbef5a39e0e36a60720cf00000000": "00",
|
||||
"7400000001430000000110fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000": "00",
|
||||
"740000000143000000011e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000": "00",
|
||||
"7400000001430000000124ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "00",
|
||||
"7400000001430000000134d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "00",
|
||||
"7400000001430000000135631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000": "00",
|
||||
"740000000143000000013f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000": "00",
|
||||
"740000000143000000014341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "00",
|
||||
"740000000143000000014d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "00",
|
||||
"740000000143000000014f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "00",
|
||||
"740000000143000000015491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000": "00",
|
||||
"740000000143000000015e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000": "00",
|
||||
"74000000014300000001711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000": "00",
|
||||
"7400000001430000000176cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000": "00",
|
||||
"740000000143000000018bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000": "00",
|
||||
"74000000014300000001943082f6babcfc5a5799956e8149574ab83cd8f474c6d2958fd8a78ed5b5e73d00000000": "00",
|
||||
"74000000014300000001996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000": "00",
|
||||
"74000000014300000001aa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000": "00",
|
||||
"74000000014300000001acdda30e345a698a43e7d105719c9bbae846351caa6e59c5234cc1eb4840e6a300000000": "00",
|
||||
"74000000014300000001b4bec8d2a53d512fe9c55d5af3c9565302e233cb8dab73b937791abb9cc41faf00000000": "00",
|
||||
"74000000014300000001b6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000": "00",
|
||||
"74000000014300000001bdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000": "00",
|
||||
"74000000014300000001da74c9e20a2fef35b4363a266dda1141a65d1c79d2d59ab85a1dd201731b3f8400000000": "00",
|
||||
"74000000014300000001e66914b1f93cf6d3317090f01c11aa2614f2c61fca5e22bfbd0192771222676900000000": "00",
|
||||
"74000000014300000001e7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000": "00",
|
||||
"74000000014300000001f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "00",
|
||||
"74000000014300000001f74e991d682a2b3b97e69bfebff3e7a446b528f44db2f59bf508fa206808c13800000000": "00",
|
||||
"74000000014300000001f75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000": "00",
|
||||
"7400000001534800000000000000333896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "00",
|
||||
"74000000015348000000000000003399fe9437e4242bb7c6107f5b9131f807ead2ad0c70dfe039b34d1f801a51bc0500000000": "00",
|
||||
"7400000001534800000000000000339c98d2db8df0a5d4b5b1ee3074f872541b4c9fccd5e13256915ff22049faaf1d00000000": "00",
|
||||
"740000000153480000000000000033d52fb57ff1d3a4e5e2f923421015e2b5ce392f8031dfcdbbf6e3d425fef19e5a00000000": "00",
|
||||
"740000000153480000000000000033eb9dc1183d33bbb09bf6daa19e2a9c485fa5dff57386872a020f01e1fa14bf3c00000000": "00",
|
||||
"740000000153480000000000000033ed9f7353e70f6b38e26b0433245dde90b348ac1996f50d10c827a29c5240c24100000000": "00",
|
||||
"740000000153480000000000000033fa5c2be8b0a338c4bd7200c9a6cfcdc2657f80acfa9dbef5a39e0e36a60720cf00000000": "00",
|
||||
"740000000153480000000000000034b88b14dfc52612e8e1b84550d5ab7136d591302185938a7c59b54077c5a723f000000000": "00",
|
||||
"7400000001534800000000000000370a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "00",
|
||||
"740000000153480000000000000038de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "00",
|
||||
"74000000015348000000000000003aac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "00",
|
||||
"74000000015348000000000000003aac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000001": "00",
|
||||
"7400000001534800000000000000415ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000001": "00",
|
||||
"740000000153480000000000000047eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "00",
|
||||
"7400000001534800000000ffffffff0c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000": "00",
|
||||
"7400000001534800000000ffffffff17b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000": "00",
|
||||
"7400000001534800000000ffffffff240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000": "00",
|
||||
"7400000001534800000000ffffffff26ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000": "00",
|
||||
"7400000001534800000000ffffffff28dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "00",
|
||||
"7400000001534800000000ffffffff3dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000": "00",
|
||||
"7400000001534800000000ffffffff6b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000": "00",
|
||||
"7400000001534800000000ffffffff6e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000": "00",
|
||||
"7400000001534800000000ffffffff7284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000": "00",
|
||||
"7400000001534800000000ffffffff7541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000": "00",
|
||||
"7400000001534800000000ffffffffc86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000": "00",
|
||||
"7400000001534800000000ffffffffcf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000": "00",
|
||||
"7400000001534800000000ffffffffd5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000": "00",
|
||||
"7400000001534800000000ffffffffd9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000": "00",
|
||||
"7400000001534800000000ffffffffe63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000": "00",
|
||||
"7400000001534800000000ffffffffee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000": "00",
|
||||
"74000000015348000000010000003334d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "00",
|
||||
"7400000001534800000001000000334341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "00",
|
||||
"7400000001534800000001000000334d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "00",
|
||||
"740000000153480000000100000033943082f6babcfc5a5799956e8149574ab83cd8f474c6d2958fd8a78ed5b5e73d00000000": "00",
|
||||
"740000000153480000000100000033b4bec8d2a53d512fe9c55d5af3c9565302e233cb8dab73b937791abb9cc41faf00000000": "00",
|
||||
"740000000153480000000100000033da74c9e20a2fef35b4363a266dda1141a65d1c79d2d59ab85a1dd201731b3f8400000000": "00",
|
||||
"740000000153480000000100000033e66914b1f93cf6d3317090f01c11aa2614f2c61fca5e22bfbd0192771222676900000000": "00",
|
||||
"740000000153480000000100000033f74e991d682a2b3b97e69bfebff3e7a446b528f44db2f59bf508fa206808c13800000000": "00",
|
||||
"740000000153480000000100000035acdda30e345a698a43e7d105719c9bbae846351caa6e59c5234cc1eb4840e6a300000000": "00",
|
||||
"74000000015348000000010000003724ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "00",
|
||||
"7400000001534800000001000000384f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "00",
|
||||
"7400000001534800000001ffffffff10fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000": "00",
|
||||
"7400000001534800000001ffffffff1e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000": "00",
|
||||
"7400000001534800000001ffffffff35631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000": "00",
|
||||
"7400000001534800000001ffffffff3f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000": "00",
|
||||
"7400000001534800000001ffffffff5491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000": "00",
|
||||
"7400000001534800000001ffffffff5e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000": "00",
|
||||
"7400000001534800000001ffffffff711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000": "00",
|
||||
"7400000001534800000001ffffffff76cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000": "00",
|
||||
"7400000001534800000001ffffffff8bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000": "00",
|
||||
"7400000001534800000001ffffffff996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000": "00",
|
||||
"7400000001534800000001ffffffffaa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000": "00",
|
||||
"7400000001534800000001ffffffffb6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000": "00",
|
||||
"7400000001534800000001ffffffffbdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000": "00",
|
||||
"7400000001534800000001ffffffffe7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000": "00",
|
||||
"7400000001534800000001fffffffff101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "00",
|
||||
"7400000001534800000001fffffffff75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000": "00",
|
||||
"740000000153550000000000000000000f322828dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "00",
|
||||
"740000000153550000000000000000000f42400c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000": "00",
|
||||
"740000000153550000000000000000000f424017b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000": "00",
|
||||
"740000000153550000000000000000000f4240240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000": "00",
|
||||
"740000000153550000000000000000000f424026ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000": "00",
|
||||
"740000000153550000000000000000000f42403dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000": "00",
|
||||
"740000000153550000000000000000000f42406b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000": "00",
|
||||
"740000000153550000000000000000000f42406e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000": "00",
|
||||
"740000000153550000000000000000000f42407284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000": "00",
|
||||
"740000000153550000000000000000000f42407541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000": "00",
|
||||
"740000000153550000000000000000000f4240c86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000": "00",
|
||||
"740000000153550000000000000000000f4240cf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000": "00",
|
||||
"740000000153550000000000000000000f4240d5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000": "00",
|
||||
"740000000153550000000000000000000f4240d9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000": "00",
|
||||
"740000000153550000000000000000000f4240e63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000": "00",
|
||||
"740000000153550000000000000000000f4240ee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000": "00",
|
||||
"740000000153550000000100000000000f3228f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "00",
|
||||
"740000000153550000000100000000000f424010fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000": "00",
|
||||
"740000000153550000000100000000000f42401e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000": "00",
|
||||
"740000000153550000000100000000000f424035631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000": "00",
|
||||
"740000000153550000000100000000000f42403f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000": "00",
|
||||
"740000000153550000000100000000000f42405491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000": "00",
|
||||
"740000000153550000000100000000000f42405e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000": "00",
|
||||
"740000000153550000000100000000000f4240711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000": "00",
|
||||
"740000000153550000000100000000000f424076cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000": "00",
|
||||
"740000000153550000000100000000000f42408bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000": "00",
|
||||
"740000000153550000000100000000000f4240996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000": "00",
|
||||
"740000000153550000000100000000000f4240aa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000": "00",
|
||||
"740000000153550000000100000000000f4240b6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000": "00",
|
||||
"740000000153550000000100000000000f4240bdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000": "00",
|
||||
"740000000153550000000100000000000f4240e7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000": "00",
|
||||
"740000000153550000000100000000000f4240f75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000": "00",
|
||||
"74000000015356000000000000000000000000ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "00",
|
||||
"740000000153560000000000000000000f09d85ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000001": "00",
|
||||
"740000000153560000000000000000000f2c9ceb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "00",
|
||||
"740000000153560000000000000000000f32280a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "00",
|
||||
"740000000153560000000000000000000f3228de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "00",
|
||||
"740000000153560000000000000000000f337cac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000001": "00",
|
||||
"740000000153560000000000000000000f42403896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "00",
|
||||
"740000000153560000000000000000000f424099fe9437e4242bb7c6107f5b9131f807ead2ad0c70dfe039b34d1f801a51bc0500000000": "00",
|
||||
"740000000153560000000000000000000f42409c98d2db8df0a5d4b5b1ee3074f872541b4c9fccd5e13256915ff22049faaf1d00000000": "00",
|
||||
"740000000153560000000000000000000f4240b88b14dfc52612e8e1b84550d5ab7136d591302185938a7c59b54077c5a723f000000000": "00",
|
||||
"740000000153560000000000000000000f4240d52fb57ff1d3a4e5e2f923421015e2b5ce392f8031dfcdbbf6e3d425fef19e5a00000000": "00",
|
||||
"740000000153560000000000000000000f4240eb9dc1183d33bbb09bf6daa19e2a9c485fa5dff57386872a020f01e1fa14bf3c00000000": "00",
|
||||
"740000000153560000000000000000000f4240ed9f7353e70f6b38e26b0433245dde90b348ac1996f50d10c827a29c5240c24100000000": "00",
|
||||
"740000000153560000000000000000000f4240fa5c2be8b0a338c4bd7200c9a6cfcdc2657f80acfa9dbef5a39e0e36a60720cf00000000": "00",
|
||||
"740000000153560000000100000000000f322824ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "00",
|
||||
"740000000153560000000100000000000f32284f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "00",
|
||||
"740000000153560000000100000000000f424034d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "00",
|
||||
"740000000153560000000100000000000f42404341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "00",
|
||||
"740000000153560000000100000000000f42404d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "00",
|
||||
"740000000153560000000100000000000f4240943082f6babcfc5a5799956e8149574ab83cd8f474c6d2958fd8a78ed5b5e73d00000000": "00",
|
||||
"740000000153560000000100000000000f4240acdda30e345a698a43e7d105719c9bbae846351caa6e59c5234cc1eb4840e6a300000000": "00",
|
||||
"740000000153560000000100000000000f4240b4bec8d2a53d512fe9c55d5af3c9565302e233cb8dab73b937791abb9cc41faf00000000": "00",
|
||||
"740000000153560000000100000000000f4240da74c9e20a2fef35b4363a266dda1141a65d1c79d2d59ab85a1dd201731b3f8400000000": "00",
|
||||
"740000000153560000000100000000000f4240e66914b1f93cf6d3317090f01c11aa2614f2c61fca5e22bfbd0192771222676900000000": "00",
|
||||
"740000000153560000000100000000000f4240f74e991d682a2b3b97e69bfebff3e7a446b528f44db2f59bf508fa206808c13800000000": "00",
|
||||
"740000000153680000003334d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "00",
|
||||
"74000000015368000000333896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "00",
|
||||
"74000000015368000000334341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "00",
|
||||
"74000000015368000000334d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "00",
|
||||
"7400000001536800000033943082f6babcfc5a5799956e8149574ab83cd8f474c6d2958fd8a78ed5b5e73d00000000": "00",
|
||||
"740000000153680000003399fe9437e4242bb7c6107f5b9131f807ead2ad0c70dfe039b34d1f801a51bc0500000000": "00",
|
||||
"74000000015368000000339c98d2db8df0a5d4b5b1ee3074f872541b4c9fccd5e13256915ff22049faaf1d00000000": "00",
|
||||
"7400000001536800000033b4bec8d2a53d512fe9c55d5af3c9565302e233cb8dab73b937791abb9cc41faf00000000": "00",
|
||||
"7400000001536800000033d52fb57ff1d3a4e5e2f923421015e2b5ce392f8031dfcdbbf6e3d425fef19e5a00000000": "00",
|
||||
"7400000001536800000033da74c9e20a2fef35b4363a266dda1141a65d1c79d2d59ab85a1dd201731b3f8400000000": "00",
|
||||
"7400000001536800000033e66914b1f93cf6d3317090f01c11aa2614f2c61fca5e22bfbd0192771222676900000000": "00",
|
||||
"7400000001536800000033eb9dc1183d33bbb09bf6daa19e2a9c485fa5dff57386872a020f01e1fa14bf3c00000000": "00",
|
||||
"7400000001536800000033ed9f7353e70f6b38e26b0433245dde90b348ac1996f50d10c827a29c5240c24100000000": "00",
|
||||
"7400000001536800000033f74e991d682a2b3b97e69bfebff3e7a446b528f44db2f59bf508fa206808c13800000000": "00",
|
||||
"7400000001536800000033fa5c2be8b0a338c4bd7200c9a6cfcdc2657f80acfa9dbef5a39e0e36a60720cf00000000": "00",
|
||||
"7400000001536800000034b88b14dfc52612e8e1b84550d5ab7136d591302185938a7c59b54077c5a723f000000000": "00",
|
||||
"7400000001536800000035acdda30e345a698a43e7d105719c9bbae846351caa6e59c5234cc1eb4840e6a300000000": "00",
|
||||
"74000000015368000000370a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "00",
|
||||
"740000000153680000003724ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "00",
|
||||
"74000000015368000000384f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "00",
|
||||
"7400000001536800000038de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "00",
|
||||
"740000000153680000003aac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "00",
|
||||
"740000000153680000003aac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000001": "00",
|
||||
"74000000015368000000415ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000001": "00",
|
||||
"7400000001536800000047eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "00",
|
||||
"74000000015368ffffffff0c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000": "00",
|
||||
"74000000015368ffffffff10fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000": "00",
|
||||
"74000000015368ffffffff17b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000": "00",
|
||||
"74000000015368ffffffff1e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000": "00",
|
||||
"74000000015368ffffffff240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000": "00",
|
||||
"74000000015368ffffffff26ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000": "00",
|
||||
"74000000015368ffffffff28dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "00",
|
||||
"74000000015368ffffffff35631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000": "00",
|
||||
"74000000015368ffffffff3dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000": "00",
|
||||
"74000000015368ffffffff3f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000": "00",
|
||||
"74000000015368ffffffff5491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000": "00",
|
||||
"74000000015368ffffffff5e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000": "00",
|
||||
"74000000015368ffffffff6b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000": "00",
|
||||
"74000000015368ffffffff6e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000": "00",
|
||||
"74000000015368ffffffff711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000": "00",
|
||||
"74000000015368ffffffff7284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000": "00",
|
||||
"74000000015368ffffffff7541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000": "00",
|
||||
"74000000015368ffffffff76cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000": "00",
|
||||
"74000000015368ffffffff8bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000": "00",
|
||||
"74000000015368ffffffff996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000": "00",
|
||||
"74000000015368ffffffffaa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000": "00",
|
||||
"74000000015368ffffffffb6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000": "00",
|
||||
"74000000015368ffffffffbdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000": "00",
|
||||
"74000000015368ffffffffc86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000": "00",
|
||||
"74000000015368ffffffffcf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000": "00",
|
||||
"74000000015368ffffffffd5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000": "00",
|
||||
"74000000015368ffffffffd9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000": "00",
|
||||
"74000000015368ffffffffe63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000": "00",
|
||||
"74000000015368ffffffffe7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000": "00",
|
||||
"74000000015368ffffffffee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000": "00",
|
||||
"74000000015368fffffffff101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "00",
|
||||
"74000000015368fffffffff75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000": "00",
|
||||
"7400000001537500000000000f322828dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "00",
|
||||
"7400000001537500000000000f3228f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "00",
|
||||
"7400000001537500000000000f42400c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000": "00",
|
||||
"7400000001537500000000000f424010fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000": "00",
|
||||
"7400000001537500000000000f424017b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000": "00",
|
||||
"7400000001537500000000000f42401e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000": "00",
|
||||
"7400000001537500000000000f4240240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000": "00",
|
||||
"7400000001537500000000000f424026ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000": "00",
|
||||
"7400000001537500000000000f424035631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000": "00",
|
||||
"7400000001537500000000000f42403dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000": "00",
|
||||
"7400000001537500000000000f42403f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000": "00",
|
||||
"7400000001537500000000000f42405491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000": "00",
|
||||
"7400000001537500000000000f42405e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000": "00",
|
||||
"7400000001537500000000000f42406b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000": "00",
|
||||
"7400000001537500000000000f42406e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000": "00",
|
||||
"7400000001537500000000000f4240711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000": "00",
|
||||
"7400000001537500000000000f42407284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000": "00",
|
||||
"7400000001537500000000000f42407541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000": "00",
|
||||
"7400000001537500000000000f424076cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000": "00",
|
||||
"7400000001537500000000000f42408bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000": "00",
|
||||
"7400000001537500000000000f4240996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000": "00",
|
||||
"7400000001537500000000000f4240aa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000": "00",
|
||||
"7400000001537500000000000f4240b6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000": "00",
|
||||
"7400000001537500000000000f4240bdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000": "00",
|
||||
"7400000001537500000000000f4240c86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000": "00",
|
||||
"7400000001537500000000000f4240cf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000": "00",
|
||||
"7400000001537500000000000f4240d5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000": "00",
|
||||
"7400000001537500000000000f4240d9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000": "00",
|
||||
"7400000001537500000000000f4240e63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000": "00",
|
||||
"7400000001537500000000000f4240e7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000": "00",
|
||||
"7400000001537500000000000f4240ee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000": "00",
|
||||
"7400000001537500000000000f4240f75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000": "00",
|
||||
"740000000153760000000000000000ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "00",
|
||||
"7400000001537600000000000f09d85ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000001": "00",
|
||||
"7400000001537600000000000f2c9ceb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "00",
|
||||
"7400000001537600000000000f32280a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "00",
|
||||
"7400000001537600000000000f322824ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "00",
|
||||
"7400000001537600000000000f32284f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "00",
|
||||
"7400000001537600000000000f3228de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "00",
|
||||
"7400000001537600000000000f337cac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000001": "00",
|
||||
"7400000001537600000000000f424034d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "00",
|
||||
"7400000001537600000000000f42403896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "00",
|
||||
"7400000001537600000000000f42404341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "00",
|
||||
"7400000001537600000000000f42404d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "00",
|
||||
"7400000001537600000000000f4240943082f6babcfc5a5799956e8149574ab83cd8f474c6d2958fd8a78ed5b5e73d00000000": "00",
|
||||
"7400000001537600000000000f424099fe9437e4242bb7c6107f5b9131f807ead2ad0c70dfe039b34d1f801a51bc0500000000": "00",
|
||||
"7400000001537600000000000f42409c98d2db8df0a5d4b5b1ee3074f872541b4c9fccd5e13256915ff22049faaf1d00000000": "00",
|
||||
"7400000001537600000000000f4240acdda30e345a698a43e7d105719c9bbae846351caa6e59c5234cc1eb4840e6a300000000": "00",
|
||||
"7400000001537600000000000f4240b4bec8d2a53d512fe9c55d5af3c9565302e233cb8dab73b937791abb9cc41faf00000000": "00",
|
||||
"7400000001537600000000000f4240b88b14dfc52612e8e1b84550d5ab7136d591302185938a7c59b54077c5a723f000000000": "00",
|
||||
"7400000001537600000000000f4240d52fb57ff1d3a4e5e2f923421015e2b5ce392f8031dfcdbbf6e3d425fef19e5a00000000": "00",
|
||||
"7400000001537600000000000f4240da74c9e20a2fef35b4363a266dda1141a65d1c79d2d59ab85a1dd201731b3f8400000000": "00",
|
||||
"7400000001537600000000000f4240e66914b1f93cf6d3317090f01c11aa2614f2c61fca5e22bfbd0192771222676900000000": "00",
|
||||
"7400000001537600000000000f4240eb9dc1183d33bbb09bf6daa19e2a9c485fa5dff57386872a020f01e1fa14bf3c00000000": "00",
|
||||
"7400000001537600000000000f4240ed9f7353e70f6b38e26b0433245dde90b348ac1996f50d10c827a29c5240c24100000000": "00",
|
||||
"7400000001537600000000000f4240f74e991d682a2b3b97e69bfebff3e7a446b528f44db2f59bf508fa206808c13800000000": "00",
|
||||
"7400000001537600000000000f4240fa5c2be8b0a338c4bd7200c9a6cfcdc2657f80acfa9dbef5a39e0e36a60720cf00000000": "00",
|
||||
"7400000001630a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "000000003700000028320f00000000000014a62c71694f8bf7583bfe73bd1b2235b81ee125580000000001",
|
||||
"7400000001630c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000": "00000000ffffffff40420f00000000000014cc0fdce38328d8df1f900a638544b15f9d81f5520000000000",
|
||||
"74000000016310fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000": "00000000ffffffff40420f000000000000143ddd945091f3ec8dc5e0c0cefce32875e5037aeb0000000000",
|
||||
"74000000016317b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000": "00000000ffffffff40420f00000000000014c53ec93147b4fa7e1e1bf56f0edbdd319895e2530000000000",
|
||||
"7400000001631e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000": "00000000ffffffff40420f0000000000001427595b8603244dc6dd42d5a89d6e77a18f18e23b0000000000",
|
||||
"740000000163240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000": "00000000ffffffff40420f00000000000014e4a65896f371f330fd6aa119e933fc346cb1223b0000000000",
|
||||
"74000000016324ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "000000003700000028320f00000000000014f0f269d0cb5eb1e065b1084d1d787a7ec5b6ed8c0000000001",
|
||||
"74000000016326ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000": "00000000ffffffff40420f0000000000001401ef877cb2f639d44cb257bb2eb983d408c8893c0000000000",
|
||||
"74000000016328dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "00000000ffffffff28320f000000000000141fcdaa5bd5efa8811106f6390e5ecc327083426b0000000001",
|
||||
"74000000016334d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "000000003300000040420f000000000000145cb324e109b920b19494549b3e540f57d43c4ef10000000100",
|
||||
"74000000016335631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000": "00000000ffffffff40420f00000000000014ad37f7abb46c43e2ac8238c1c9e4a98841b3fa6c0000000000",
|
||||
"7400000001633896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "000000003300000040420f00000000000014b9e7138dd8a5fdd11fee499ccb7a6cd131edbaa30000000100",
|
||||
"7400000001633dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000": "00000000ffffffff40420f00000000000014198a5f555dc8f78bcc35bf23baefc03c954e70530000000000",
|
||||
"7400000001633f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000": "00000000ffffffff40420f00000000000014a2affbcd20614676abcac636b5db306e964411210000000000",
|
||||
"7400000001634341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "000000003300000040420f000000000000142667d9e317cf8f0aac2543fd27f38e0ab9da3ecb0000000100",
|
||||
"7400000001634d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "000000003300000040420f00000000000014d20cf3c6172ea38a0ec42514435978a76a99b1eb0000000100",
|
||||
"7400000001634f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "000000003800000028320f00000000000014c674ba19506d9e2c178ab0eb4acd47b7011927050000000001",
|
||||
"7400000001635491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000": "00000000ffffffff40420f000000000000140ca598cff494738bfbfe1b06f416bce1def2dfae0000000000",
|
||||
"7400000001635e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000": "00000000ffffffff40420f00000000000014045216fd9b9f0aeff2be3c18327c3083fc4ffd4a0000000000",
|
||||
"7400000001635ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000001": "0000000041000000d8090f0000000000001460976bb97754c7ab48bac4c894d27a4126bc23d10000000001",
|
||||
"7400000001636b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000": "00000000ffffffff40420f00000000000014433b4a5bdd977ac6ab958e07834a08fbd2e7f9610000000000",
|
||||
"7400000001636e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000": "00000000ffffffff40420f00000000000014edda79fa7ce1811ea0c0a9a2ffca881c509269020000000000",
|
||||
"740000000163711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000": "00000000ffffffff40420f000000000000144eca2ab57c2a64d356a40fab260140a48fb39ca40000000000",
|
||||
"7400000001637284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000": "00000000ffffffff40420f000000000000144725f4cf8e0fbeb8d220c54f3e38d6d1f73cba1a0000000000",
|
||||
"7400000001637541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000": "00000000ffffffff40420f000000000000142e79df92c411266e738b4488a54686c6fe0d27b80000000000",
|
||||
"74000000016376cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000": "00000000ffffffff40420f0000000000001417e6329645e6f871f3b2f01b2e8115ffc01fa0dd0000000000",
|
||||
"7400000001638bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000": "00000000ffffffff40420f000000000000149dfcfcd6ce44031193a6960f878460c3f1157b0b0000000000",
|
||||
"740000000163943082f6babcfc5a5799956e8149574ab83cd8f474c6d2958fd8a78ed5b5e73d00000000": "000000003300000040420f00000000000014646f81d8c9569f04c93d1559a606da8185cb49c10000000000",
|
||||
"740000000163996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000": "00000000ffffffff40420f0000000000001431bbc5580d28765f2172cd537369614a87c99b4d0000000000",
|
||||
"74000000016399fe9437e4242bb7c6107f5b9131f807ead2ad0c70dfe039b34d1f801a51bc0500000000": "000000003300000040420f00000000000014cc2953e1188c162d1689206ce79fabdd54c0a5750000000000",
|
||||
"7400000001639c98d2db8df0a5d4b5b1ee3074f872541b4c9fccd5e13256915ff22049faaf1d00000000": "000000003300000040420f000000000000147220144f51a799765a1cfebc74a6ff789e0cf0980000000000",
|
||||
"740000000163aa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000": "00000000ffffffff40420f000000000000141860e07aa099551e6f6d4794de9eb48aac172a450000000000",
|
||||
"740000000163ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "000000003a000000000000000000000000144a476eeca3f6b9f47b7ac6ea4953f5aec201fda002032075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d04000000000a746573746e616d652d31000001",
|
||||
"740000000163ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000001": "000000003a0000007c330f00000000000014a7cd0f0c61bcdb86967f1b11128b7d3ba7d78ec70000000001",
|
||||
"740000000163acdda30e345a698a43e7d105719c9bbae846351caa6e59c5234cc1eb4840e6a300000000": "000000003500000040420f00000000000014359d9335692983142de60a57df5a0197fd13ab760000010000",
|
||||
"740000000163b4bec8d2a53d512fe9c55d5af3c9565302e233cb8dab73b937791abb9cc41faf00000000": "000000003300000040420f000000000000147db1e3611d2409939fb385976712f032de7629ef0000000000",
|
||||
"740000000163b6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000": "00000000ffffffff40420f000000000000141b5a09ead7ea9f4c460d1a248d6980a98186f3c70000000000",
|
||||
"740000000163b88b14dfc52612e8e1b84550d5ab7136d591302185938a7c59b54077c5a723f000000000": "000000003400000040420f00000000000014332479f977c3cd9a6a915a25e7ca4e95bbfb92c00000010000",
|
||||
"740000000163bdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000": "00000000ffffffff40420f0000000000001434079c84c47cf7b4ce95b38b8b19040043b5a58b0000000000",
|
||||
"740000000163c86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000": "00000000ffffffff40420f000000000000149df7d0326747749b602967421b892f1efe19fd730000000000",
|
||||
"740000000163cf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000": "00000000ffffffff40420f000000000000147f0a0bb9e20f94b934fb814e4938ef3a783e6f970000000000",
|
||||
"740000000163d52fb57ff1d3a4e5e2f923421015e2b5ce392f8031dfcdbbf6e3d425fef19e5a00000000": "000000003300000040420f0000000000001441136683ea0485bbef46e9d9f6a2ff766128efad0000000000",
|
||||
"740000000163d5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000": "00000000ffffffff40420f0000000000001468a14e77be363ef2ee92406d5ce969a1785110130000000000",
|
||||
"740000000163d9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000": "00000000ffffffff40420f00000000000014a37f94f98c1e2cb4714d5ce5465f9d7a82f3e36e0000000000",
|
||||
"740000000163da74c9e20a2fef35b4363a266dda1141a65d1c79d2d59ab85a1dd201731b3f8400000000": "000000003300000040420f00000000000014452b000bd69cd9c4a99322d1c41e972cdcd662fe0000000000",
|
||||
"740000000163de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "000000003800000028320f00000000000014222740511246fe2b10957049ae10e0bbe7fa996c0000000001",
|
||||
"740000000163e63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000": "00000000ffffffff40420f00000000000014bfc1d60d296b8a15c489d32d5647f21e8c6cb8950000000000",
|
||||
"740000000163e66914b1f93cf6d3317090f01c11aa2614f2c61fca5e22bfbd0192771222676900000000": "000000003300000040420f000000000000142cd7a7b49b1013603d53b6a234493ee1208471750000000000",
|
||||
"740000000163e7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000": "00000000ffffffff40420f00000000000014f59d25508e19abeec49c3377bedd48ad7947af440000000000",
|
||||
"740000000163eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000000": "0000000047000000102700000000000000142315e1a3850c7afd6f1be87df18dea1d3939d0a904032075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a00000020c616d48ae4039aad2438bc47a11e0ec0d883f5dfc1f34aa10944085c57d79a13000001",
|
||||
"740000000163eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "00000000470000009c2c0f000000000000147a98b6621e4ccc528560645f6a7682633a24fad80000000001",
|
||||
"740000000163eb9dc1183d33bbb09bf6daa19e2a9c485fa5dff57386872a020f01e1fa14bf3c00000000": "000000003300000040420f000000000000146a94c2244bd9a4b5257a8c946b68af72f4118b350000000000",
|
||||
"740000000163ed9f7353e70f6b38e26b0433245dde90b348ac1996f50d10c827a29c5240c24100000000": "000000003300000040420f000000000000142f6d31b494bdfa42034719ca28894496cdb893590000000000",
|
||||
"740000000163ee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000": "00000000ffffffff40420f000000000000147e7a6df077a7d3a39fba31246574634375439e3c0000000000",
|
||||
"740000000163f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "00000000ffffffff28320f000000000000145258eb6f21709d00f66fa39deab6e8d5391eae5f0000000001",
|
||||
"740000000163f74e991d682a2b3b97e69bfebff3e7a446b528f44db2f59bf508fa206808c13800000000": "000000003300000040420f00000000000014cfe3810f02a4af8dd98092bb6650841bac0ff2c40000000000",
|
||||
"740000000163f75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000": "00000000ffffffff40420f000000000000144931bd2996fb46b2f17a5f4bae7d0b2a04ee65bc0000000000",
|
||||
"740000000163fa5c2be8b0a338c4bd7200c9a6cfcdc2657f80acfa9dbef5a39e0e36a60720cf00000000": "000000003300000040420f000000000000144c43158dc848068c85406c14957b0cab073f343b0000000000",
|
||||
"7400000001640a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000": "000000003300000040420f000000000000144f9644e04b6bbc4f53ae49e444d005524cc1422f000000",
|
||||
"7400000001640a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000001": "000000003300000040420f000000000000140441cbe166229b9e2adb6aa90751052523cb298d000000",
|
||||
"74000000016424ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000": "000000003300000040420f00000000000014f4f1b2cd73eebe34bef87c174e5d4903e86493ff000000",
|
||||
"74000000016424ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000001": "000000003300000040420f000000000000146337e3ecd9354ba7a5fbe0ce2feb6d3bc5558a54000000",
|
||||
"74000000016428dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000": "000000003300000040420f000000000000145cb324e109b920b19494549b3e540f57d43c4ef1000000",
|
||||
"74000000016428dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000001": "000000003300000040420f00000000000014b9e7138dd8a5fdd11fee499ccb7a6cd131edbaa3000000",
|
||||
"740000000164320e6e6cffd8d9b904d97f904536c6c410f47d7ed856e354b0836b6bd47ec44b00000000": "000000003300000040420f000000000000145428d3cebe215a9f78bf3ca6d3ebc055b828f3d9000000",
|
||||
"740000000164320e6e6cffd8d9b904d97f904536c6c410f47d7ed856e354b0836b6bd47ec44b00000001": "000000003300000040420f000000000000146dddb81cee99d34e617876a2d4bd091fea7da844000000",
|
||||
"7400000001644f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000": "000000003300000040420f00000000000014f7c2c1cf022472febc2deb36c91ae2e450c36b29000000",
|
||||
"7400000001644f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000001": "000000003300000040420f00000000000014309a0821ae883849b41aeb44c24b075af338b8c0000000",
|
||||
"7400000001645ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000000": "000000003300000040420f000000000000142a52b414589aca0f9e6b79be9563e3e0b8e053aa000000",
|
||||
"74000000016499e639a2ab769f77f51c3752ca4d85436abd3d6ee0fa28d249fc67a7f4e8422200000000": "000000003300000040420f00000000000014e154c1b406841cc45d1d63fbd0c36003b5e34cc9000000",
|
||||
"74000000016499e639a2ab769f77f51c3752ca4d85436abd3d6ee0fa28d249fc67a7f4e8422200000001": "000000003300000040420f000000000000149d03a12b2facd319be3b174ac49e78effae0f9fc000000",
|
||||
"740000000164ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000": "000000003300000040420f00000000000014b27ecd0a361b0fe9acafecdc47841e021a4ebf50000000",
|
||||
"740000000164de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000": "000000003300000040420f0000000000001499b6d711eb6ba45313ed8ff37d9a8ec5df572750000000",
|
||||
"740000000164de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000001": "000000003300000040420f000000000000149259252916ee5b5573f1f911ed49c99592b1db44000000",
|
||||
"740000000164eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000000": "0000000041000000102700000000000000142315e1a3850c7afd6f1be87df18dea1d3939d0a903042075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a0000000a746573746e616d652d3120d239a4dc0ce73cc36bf38204e3b0a438d353a7c6d57889fa6e88fff91265145500",
|
||||
"740000000164eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000001": "000000003300000040420f000000000000148d375a0132f2077de9c4a96c66e15c24c5501851000000",
|
||||
"740000000164f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000": "000000003300000040420f000000000000142667d9e317cf8f0aac2543fd27f38e0ab9da3ecb000000",
|
||||
"740000000164f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000001": "000000003300000040420f00000000000014d20cf3c6172ea38a0ec42514435978a76a99b1eb000000",
|
||||
"74000000017303681f8ee2b9bd28df7af7445a589052965b60c4d89d13269b483aafd0ea9db600000000": "6b1c5e65d3a687a5a016e242dea9aa49e55c870c925c65ac040011f52d9f1f2500000000",
|
||||
"7400000001730fff2e108fcc22c30f2e5117182fc26f74cd2a79c3372d196e7eff08416276d500000000": "0a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546400000000",
|
||||
"74000000017310576dd1a93f478542464d6721bc6053cdd310f68241cf7f94864edc80d8f23b00000000": "bdab5449ac51a2a6c35c9ccff7aaecec0d2568fda855be526c32588ec72d69aa00000000",
|
||||
"74000000017313642f3ae0597fae5368a9ab3aa5002159ee421b622fff2dfaf4431259e117fb00000000": "0a1ad3d1abddd4df701750e546180af3fe4d48961022190397e30676d478546401000000",
|
||||
"7400000001731372f66217b7c16d46080b70af90951addb9fdf9a148566208957b82b5b1771200000000": "24ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59900000000",
|
||||
"7400000001731d14f5deb4d7480fe6568ede44151397fc4fa3da9ed32b6c405ca946d12e937200000000": "1e97ca90dc61c77eadb5db80fa3ac3062656798774adc73d70aee6c06a50463a00000000",
|
||||
"7400000001732858e5aedda20aa45293fa3aa64722c13ea3d18adc8e952728739e450ef2023b00000000": "b6426c7489bacaeeb20e1f604b6ff5c7eb68d3c584bcfc82bdb68b4fb9bba6c800000000",
|
||||
"7400000001732e1c6abb0f6ed5b4fcc53b6ebccaf5f155e0fa7d763a12a31ae68ac9408d02fd00000000": "26ff1a281d3266513c3cb4ff75bf500254648353ef3b7e28e057867baf0e751400000000",
|
||||
"740000000173323b9dd49fb66e770de1fdd46bb57028a778e140de37a7ca5596a57100defbf900000000": "24ee0ed46218285f10508ceedfec794a59e1d44bd91bad90361e8479aaced59901000000",
|
||||
"74000000017334d0a5fad161ea5893dd922e816752e4fc3652eb9ad25379e67691f6614ab92e00000000": "28dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7700000000",
|
||||
"74000000017336b9062567c977a2b897f8cbd547ce78fa3bd598ef1c6b5499e71229514fe3ed00000000": "e63ce0cd1c74654ac8e84cb2978a0c78cfc24778e41665531326cae89ee351cd00000000",
|
||||
"74000000017337066d4d4d245fc5bc01fe8c55005c9b92d7a9f8deb443a9369d4423acd9570000000000": "ee075af0b9b68a3bdf7ccaca9da87c8204a7cdbe2bf4a3590dfd8f6c8e795de000000000",
|
||||
"7400000001733896a471d160df8172db2c4e2de390e495c8f2372ecfcc189aa90244ee41f39400000000": "28dbd27362bb650701ee1471cb8fd79c44009abc8e60d032bc319ac619819d7701000000",
|
||||
"7400000001733a3126fbbf206d75ecfbb40d5da8f39068263a6dbaa4e047529fd3ddb11fd8b800000000": "de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d400000000",
|
||||
"7400000001733ad9ed2ba729a6419907e522c55e246fa70eebaae3a01a3794b2df42fc36efb500000000": "7284e421e893d7eccdde27728d97f211d8fe772d724e042ef4b145c0c0fcee7b00000000",
|
||||
"74000000017340660cb50912d86a4ec527cd5af973233d7f9bdee7361bf7fadfdf1243bfe43800000000": "240829d7a55927ea680e422b87dac74216025c62649342751826c2756726bdcc00000000",
|
||||
"740000000173408c946546253b8ba7642a80c1089bfe1a0fa76c33627d6f2d3684ee17b55ebe00000000": "de2437c61766ef9b1b53fc660f88c79e23473d34e80d84737da2f3e14526f7d401000000",
|
||||
"740000000173425ac3ec38bb16ae168a5fc25a8702bc65a6d9cd09f6896e1dc28499e2b18da800000000": "3dc0c6fa07af38bbfb9820ebd7b4965129c733676557be966c8669618a27449400000000",
|
||||
"7400000001734341692bca57bec186da5537e3718d59b1296bddfbbc758be9f8e20b27e6cac800000000": "f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf00000000",
|
||||
"7400000001734561925263b2563548dd72b116034abee46210e88371f763563b6cd33eb0053500000000": "99e639a2ab769f77f51c3752ca4d85436abd3d6ee0fa28d249fc67a7f4e8422200000000",
|
||||
"7400000001734d32785a36482a631d9e21961cfc72a5c1cc9e24ae88c7604d01e331af81fb8000000000": "f101ad4568fae51d9a9ce3bcf425eaf5e48ced07a25b96b08569226f36e49aaf01000000",
|
||||
"7400000001734dfc35d2d60a286f00bcff7fe5a868279ca21ec1903c1ef089facae0131bcbb600000000": "99e639a2ab769f77f51c3752ca4d85436abd3d6ee0fa28d249fc67a7f4e8422201000000",
|
||||
"740000000173515b89fe7e7cd16dea4e1981b4f75e0a02b896ad4d30d2200daf82d8956ba8af00000000": "10fabcf64220ce07b43d1a127f4a97df50e4e0123541ecb02bfef2422307375d00000000",
|
||||
"74000000017353cb0828e3d769a6c997319c775e387c41280157d65fda2c9b997af84d7f90db00000000": "d9352f22bac4f4ecb764cd61281ff7798bf15124749a6b0a3ea0ba9d3bd9db0500000000",
|
||||
"7400000001735442c34bd79edaa96143c2fb37fa65fd304c65fc65e154cfc67468645c402f1b00000000": "35631837f407b50a52a2d998683dcc7b9ec10d821676937810c02e7a19e6e1bb00000000",
|
||||
"74000000017354f473602bcde9d4f9f719fd4e3edabf323f5a3593adb409e49c10d549ce834800000000": "4f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a00000000",
|
||||
"7400000001735991ca040bea6b28ed9f7b388ecd3e8b5afe19fec959bda94c6ea523aef0b30000000000": "3f62e0a98011e9944e9620a822da03b9012438fee2c1372062ed98abcd32a85400000000",
|
||||
"7400000001735ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000000": "eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed00000000",
|
||||
"74000000017362ffbfd09fd83be044075929214ab559e3b9994197a792cc60b48ad80caff85700000000": "ac60693db941398eceb9e4413b3c5c1508e47d2cb7545caa58fb9c03200ddf4700000000",
|
||||
"7400000001736b96d4992c1901179bcff23c0cb649ae98f6b878ab8961341d359944494874d000000000": "aa64d26f673b9b27bb62d455a968cb95971f85070ad2915c5c94969136cc70f200000000",
|
||||
"7400000001736cdc4bd0ac996647c5e8b59a7f1616ce613ce3b4a9725a62a3abf4c7c43d114f00000000": "5ea79940ff73064890402b5b6580072e4959182fccfb5db3b89140c481694a5f00000000",
|
||||
"7400000001736dda8d21c1c543e52fa2201dd186dda5bce740cbe979e31d8c92bf6936e68bfa00000000": "996bf17c792ea9070d646bd82bf3fdc78b823e426c9bf85206ae0608ba27bbfe00000000",
|
||||
"74000000017377404f33697b378fb347d8d1fc6d341468f19cf2aaf7a2daa21679e0e5b4c5df00000000": "e7ef08ed598381b76a34363425477658dc01c38a833b728cec7d937cad72156100000000",
|
||||
"7400000001737858cecbeefa2985c7ff7a126143e1099f6c2a1e6bb8f8b49876284c5aef11b500000000": "4f4c5a016a435ee5f8ab1ff3642be1baff3f44882449d3e7dbf1251ba7b1603a01000000",
|
||||
"7400000001737a669bbd9b6071db8e6032adfdc266ece43a0375cbdda6e77925b855e2f066e000000000": "320e6e6cffd8d9b904d97f904536c6c410f47d7ed856e354b0836b6bd47ec44b00000000",
|
||||
"7400000001737e905920371ec5f99176f13c5d5010758493c830fe520e97cc4f9d66d80b3efe00000000": "c86df0977b2121bcdd8cc465c3393d74f004f8f1588520964df696d4685033a900000000",
|
||||
"7400000001737f161a9bd7c781ec7c25e094a012bd1931dec18faa2eaa5fd56b20ee19b1447100000000": "6e6a8c6f3d1b6e2cc085eb20443b20fbd704a807a5309249bb5ab5dd02b70d7500000000",
|
||||
"740000000173807957571a3add9343a9d932cb90955446f9797c984c8582176e5d8e5790f12600000000": "eb3a4a4640e638d26a3f7d42c34867e0d49635db3a42329061ae5262357a29ed01000000",
|
||||
"7400000001738486b24023f38c8f595dcb48880e9673e1e0434d601f1e8871ed60a8acda154d00000000": "f75e805a1eab20b20b22a3fe820f38fb984a80fe4e13eb0cba96598585b3177300000000",
|
||||
"74000000017389220598e0a127f887cf16b37c62f26d9cace85a448243522f9e41b408ade26400000000": "17b8aff83a89964237b449c9871e4db5f1c4912faa77af62cadf977b6d166d0100000000",
|
||||
"7400000001738dbb3eb02c8f8cf6cf03346a72d733d4ca9b7e7e5dacc1afeddfb5697376c30800000000": "0c20979f6b1d9816391792dc327ed2aa94016d929ccf6cf1cf4b4b3b87ebf68100000000",
|
||||
"7400000001738eff95c81bcf3d5467c32ce9abf81872481c568c9f90947580122e8fe177dc7000000000": "320e6e6cffd8d9b904d97f904536c6c410f47d7ed856e354b0836b6bd47ec44b01000000",
|
||||
"740000000173a2ba1d077eded3608cdf54c41c514c59b02aefe28af3e4e602f460626be42d3e00000000": "76cc5e32ce0628d7daf6a7c3c738b374aa74f9b8377c82a147bb0d77a487a35700000000",
|
||||
"740000000173a854a5b169d381f72109048ba56b41dd547ba2ac6b0b838d60d675fdcdf5310700000000": "cf044f7f4562759bc3bdce5480e567433d5bde459a981591fb2cb9a32561a71b00000000",
|
||||
"740000000173b352f75f0642ce5589167154fe760a84fb047418a871d5225fd1d66c53c4ebd100000000": "711776151e5423d47f15b71435481ad94b8081abc10eb7c2901bf2f1419e466800000000",
|
||||
"740000000173b37353c0edbb420e054eff5a0f05b3e0e99e852393f449c854057527a312fc3c00000000": "5491dba32385f57519e5723789e9cab1d341cd77587b24b1ad05a619772afe9400000000",
|
||||
"740000000173b609f6515a3dcdf32b896678a89b1d867f1bd93b7485f82d1e901c51e875b2c200000000": "d5a322102a990e4d1eff9867ee69657cca464fed60bfb8bbdb8f0f0bfd7a9c8e00000000",
|
||||
"740000000173e8634b9bc9031ba1617a827f1d51785c25bf5164692257ccbbfd3c5d2918c30d00000000": "8bc203ae3cabb037f1032e4fb403895ebc6aa60b146b5048b6a07e2578f34df000000000",
|
||||
"740000000173e899425912717de079a89b958274948c7056308ae920c899666e0cf3cfc0ffa100000000": "7541d1e00d5c900ea5113bd20f1ede588e8aefa7f07d7795e03ee151056ecaeb00000000",
|
||||
"740000000173ecf456658d108c368e6bb7473305a5278cae99cc199c55bfeb618420d6a6423100000000": "5e28176286da842abc6921ecc60d50b6c9b2976f8a40a3041bf14d3a34c6efec00000000",
|
||||
"740000000243000000001ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000": "00",
|
||||
"740000000243000000001cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000": "00",
|
||||
"740000000243000000001e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000": "00",
|
||||
"740000000243000000002ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "00",
|
||||
"740000000243000000003cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000": "00",
|
||||
"74000000024300000000458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "00",
|
||||
"740000000243000000004eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000": "00",
|
||||
"7400000002430000000053956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000": "00",
|
||||
"74000000024300000000554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "00",
|
||||
"740000000243000000005dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000001": "00",
|
||||
"7400000002430000000063864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000001": "00",
|
||||
"74000000024300000000683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "00",
|
||||
"7400000002430000000086387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000": "00",
|
||||
"740000000243000000008b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000": "00",
|
||||
"740000000243000000008eba67a23de92df548ecbb4f40be648bbf870a1de67191ad0b576c1fab59ea8b00000000": "00",
|
||||
"74000000024300000000952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "00",
|
||||
"74000000024300000000a7d8e41fbc89aacc349effd58b140f08b84ab4d33775bb967a678e8bf4d8c00a00000000": "00",
|
||||
"74000000024300000000ad6c09f1d7c9eacd449af12280c6ac397061a27bed995bc5dfe7b24ce2c49e3a00000000": "00",
|
||||
"74000000024300000000b37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000": "00",
|
||||
"74000000024300000000bf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000": "00",
|
||||
"74000000024300000000d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000000": "00",
|
||||
"74000000024300000000d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000001": "00",
|
||||
"74000000024300000000d949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000": "00",
|
||||
"74000000024300000000db3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000": "00",
|
||||
"74000000024300000000db40da9ef39f8b52f0cfc84fe1bd4320f9dcab3f0c7425af1e505cfd59ecedbb00000000": "00",
|
||||
"74000000024300000000ddef667255e254fc9d885ee04fe4852dba08d4b7ba84e86562afe6351d62098000000000": "00",
|
||||
"74000000024300000000e674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000": "00",
|
||||
"74000000024300000000ee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000": "00",
|
||||
"74000000024300000000ee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000": "00",
|
||||
"74000000024300000000fa72230236318c48e79926683a93baa4ccf09e856a78dfef9e1bf20f9b947dbe00000000": "00",
|
||||
"74000000024300000000fc7c46c4caa0d66e55e2b70c82fe65a68fef5fabfb5c903cc264afc68ea6b7bd00000000": "00",
|
||||
"740000000253480000000000000033458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "00",
|
||||
"740000000253480000000000000033554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "00",
|
||||
"7400000002534800000000000000338eba67a23de92df548ecbb4f40be648bbf870a1de67191ad0b576c1fab59ea8b00000000": "00",
|
||||
"740000000253480000000000000033ad6c09f1d7c9eacd449af12280c6ac397061a27bed995bc5dfe7b24ce2c49e3a00000000": "00",
|
||||
"740000000253480000000000000033db40da9ef39f8b52f0cfc84fe1bd4320f9dcab3f0c7425af1e505cfd59ecedbb00000000": "00",
|
||||
"740000000253480000000000000033ddef667255e254fc9d885ee04fe4852dba08d4b7ba84e86562afe6351d62098000000000": "00",
|
||||
"740000000253480000000000000033fa72230236318c48e79926683a93baa4ccf09e856a78dfef9e1bf20f9b947dbe00000000": "00",
|
||||
"740000000253480000000000000033fc7c46c4caa0d66e55e2b70c82fe65a68fef5fabfb5c903cc264afc68ea6b7bd00000000": "00",
|
||||
"740000000253480000000000000036a7d8e41fbc89aacc349effd58b140f08b84ab4d33775bb967a678e8bf4d8c00a00000000": "00",
|
||||
"740000000253480000000000000037952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "00",
|
||||
"7400000002534800000000000000382ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "00",
|
||||
"7400000002534800000000000000415dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000001": "00",
|
||||
"74000000025348000000000000004763864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000001": "00",
|
||||
"740000000253480000000000000052d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000001": "00",
|
||||
"7400000002534800000000ffffffff1ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000": "00",
|
||||
"7400000002534800000000ffffffff1cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000": "00",
|
||||
"7400000002534800000000ffffffff1e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000": "00",
|
||||
"7400000002534800000000ffffffff3cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000": "00",
|
||||
"7400000002534800000000ffffffff4eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000": "00",
|
||||
"7400000002534800000000ffffffff53956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000": "00",
|
||||
"7400000002534800000000ffffffff683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "00",
|
||||
"7400000002534800000000ffffffff86387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000": "00",
|
||||
"7400000002534800000000ffffffff8b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000": "00",
|
||||
"7400000002534800000000ffffffffb37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000": "00",
|
||||
"7400000002534800000000ffffffffbf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000": "00",
|
||||
"7400000002534800000000ffffffffd949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000": "00",
|
||||
"7400000002534800000000ffffffffdb3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000": "00",
|
||||
"7400000002534800000000ffffffffe674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000": "00",
|
||||
"7400000002534800000000ffffffffee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000": "00",
|
||||
"7400000002534800000000ffffffffee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000": "00",
|
||||
"740000000253550000000000000000000f3228683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "00",
|
||||
"740000000253550000000000000000000f42401ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000": "00",
|
||||
"740000000253550000000000000000000f42401cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000": "00",
|
||||
"740000000253550000000000000000000f42401e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000": "00",
|
||||
"740000000253550000000000000000000f42403cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000": "00",
|
||||
"740000000253550000000000000000000f42404eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000": "00",
|
||||
"740000000253550000000000000000000f424053956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000": "00",
|
||||
"740000000253550000000000000000000f424086387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000": "00",
|
||||
"740000000253550000000000000000000f42408b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000": "00",
|
||||
"740000000253550000000000000000000f4240b37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000": "00",
|
||||
"740000000253550000000000000000000f4240bf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000": "00",
|
||||
"740000000253550000000000000000000f4240d949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000": "00",
|
||||
"740000000253550000000000000000000f4240db3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000": "00",
|
||||
"740000000253550000000000000000000f4240e674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000": "00",
|
||||
"740000000253550000000000000000000f4240ee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000": "00",
|
||||
"740000000253550000000000000000000f4240ee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000": "00",
|
||||
"7400000002535600000000000000000000166cd0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000001": "00",
|
||||
"7400000002535600000000000000000002aea463864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000001": "00",
|
||||
"740000000253560000000000000000000c23a85dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000001": "00",
|
||||
"740000000253560000000000000000000f32282ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "00",
|
||||
"740000000253560000000000000000000f3228952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "00",
|
||||
"740000000253560000000000000000000f4240458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "00",
|
||||
"740000000253560000000000000000000f4240554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "00",
|
||||
"740000000253560000000000000000000f42408eba67a23de92df548ecbb4f40be648bbf870a1de67191ad0b576c1fab59ea8b00000000": "00",
|
||||
"740000000253560000000000000000000f4240a7d8e41fbc89aacc349effd58b140f08b84ab4d33775bb967a678e8bf4d8c00a00000000": "00",
|
||||
"740000000253560000000000000000000f4240ad6c09f1d7c9eacd449af12280c6ac397061a27bed995bc5dfe7b24ce2c49e3a00000000": "00",
|
||||
"740000000253560000000000000000000f4240db40da9ef39f8b52f0cfc84fe1bd4320f9dcab3f0c7425af1e505cfd59ecedbb00000000": "00",
|
||||
"740000000253560000000000000000000f4240ddef667255e254fc9d885ee04fe4852dba08d4b7ba84e86562afe6351d62098000000000": "00",
|
||||
"740000000253560000000000000000000f4240fa72230236318c48e79926683a93baa4ccf09e856a78dfef9e1bf20f9b947dbe00000000": "00",
|
||||
"740000000253560000000000000000000f4240fc7c46c4caa0d66e55e2b70c82fe65a68fef5fabfb5c903cc264afc68ea6b7bd00000000": "00",
|
||||
"7400000002536800000033458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "00",
|
||||
"7400000002536800000033554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "00",
|
||||
"74000000025368000000338eba67a23de92df548ecbb4f40be648bbf870a1de67191ad0b576c1fab59ea8b00000000": "00",
|
||||
"7400000002536800000033ad6c09f1d7c9eacd449af12280c6ac397061a27bed995bc5dfe7b24ce2c49e3a00000000": "00",
|
||||
"7400000002536800000033db40da9ef39f8b52f0cfc84fe1bd4320f9dcab3f0c7425af1e505cfd59ecedbb00000000": "00",
|
||||
"7400000002536800000033ddef667255e254fc9d885ee04fe4852dba08d4b7ba84e86562afe6351d62098000000000": "00",
|
||||
"7400000002536800000033fa72230236318c48e79926683a93baa4ccf09e856a78dfef9e1bf20f9b947dbe00000000": "00",
|
||||
"7400000002536800000033fc7c46c4caa0d66e55e2b70c82fe65a68fef5fabfb5c903cc264afc68ea6b7bd00000000": "00",
|
||||
"7400000002536800000036a7d8e41fbc89aacc349effd58b140f08b84ab4d33775bb967a678e8bf4d8c00a00000000": "00",
|
||||
"7400000002536800000037952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "00",
|
||||
"74000000025368000000382ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "00",
|
||||
"74000000025368000000415dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000001": "00",
|
||||
"740000000253680000004763864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000001": "00",
|
||||
"7400000002536800000052d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000001": "00",
|
||||
"74000000025368ffffffff1ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000": "00",
|
||||
"74000000025368ffffffff1cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000": "00",
|
||||
"74000000025368ffffffff1e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000": "00",
|
||||
"74000000025368ffffffff3cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000": "00",
|
||||
"74000000025368ffffffff4eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000": "00",
|
||||
"74000000025368ffffffff53956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000": "00",
|
||||
"74000000025368ffffffff683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "00",
|
||||
"74000000025368ffffffff86387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000": "00",
|
||||
"74000000025368ffffffff8b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000": "00",
|
||||
"74000000025368ffffffffb37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000": "00",
|
||||
"74000000025368ffffffffbf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000": "00",
|
||||
"74000000025368ffffffffd949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000": "00",
|
||||
"74000000025368ffffffffdb3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000": "00",
|
||||
"74000000025368ffffffffe674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000": "00",
|
||||
"74000000025368ffffffffee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000": "00",
|
||||
"74000000025368ffffffffee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000": "00",
|
||||
"7400000002537500000000000f3228683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "00",
|
||||
"7400000002537500000000000f42401ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000": "00",
|
||||
"7400000002537500000000000f42401cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000": "00",
|
||||
"7400000002537500000000000f42401e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000": "00",
|
||||
"7400000002537500000000000f42403cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000": "00",
|
||||
"7400000002537500000000000f42404eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000": "00",
|
||||
"7400000002537500000000000f424053956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000": "00",
|
||||
"7400000002537500000000000f424086387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000": "00",
|
||||
"7400000002537500000000000f42408b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000": "00",
|
||||
"7400000002537500000000000f4240b37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000": "00",
|
||||
"7400000002537500000000000f4240bf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000": "00",
|
||||
"7400000002537500000000000f4240d949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000": "00",
|
||||
"7400000002537500000000000f4240db3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000": "00",
|
||||
"7400000002537500000000000f4240e674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000": "00",
|
||||
"7400000002537500000000000f4240ee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000": "00",
|
||||
"7400000002537500000000000f4240ee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000": "00",
|
||||
"74000000025376000000000000166cd0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000001": "00",
|
||||
"74000000025376000000000002aea463864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000001": "00",
|
||||
"7400000002537600000000000c23a85dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000001": "00",
|
||||
"7400000002537600000000000f32282ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "00",
|
||||
"7400000002537600000000000f3228952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "00",
|
||||
"7400000002537600000000000f4240458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "00",
|
||||
"7400000002537600000000000f4240554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "00",
|
||||
"7400000002537600000000000f42408eba67a23de92df548ecbb4f40be648bbf870a1de67191ad0b576c1fab59ea8b00000000": "00",
|
||||
"7400000002537600000000000f4240a7d8e41fbc89aacc349effd58b140f08b84ab4d33775bb967a678e8bf4d8c00a00000000": "00",
|
||||
"7400000002537600000000000f4240ad6c09f1d7c9eacd449af12280c6ac397061a27bed995bc5dfe7b24ce2c49e3a00000000": "00",
|
||||
"7400000002537600000000000f4240db40da9ef39f8b52f0cfc84fe1bd4320f9dcab3f0c7425af1e505cfd59ecedbb00000000": "00",
|
||||
"7400000002537600000000000f4240ddef667255e254fc9d885ee04fe4852dba08d4b7ba84e86562afe6351d62098000000000": "00",
|
||||
"7400000002537600000000000f4240fa72230236318c48e79926683a93baa4ccf09e856a78dfef9e1bf20f9b947dbe00000000": "00",
|
||||
"7400000002537600000000000f4240fc7c46c4caa0d66e55e2b70c82fe65a68fef5fabfb5c903cc264afc68ea6b7bd00000000": "00",
|
||||
"7400000002631ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000": "00000000ffffffff40420f00000000000014ac46b87e7e6708a4f2b7e278b78ae7ba02c04b3b0000000000",
|
||||
"7400000002631cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000": "00000000ffffffff40420f00000000000014975caef4f934d0494c828c38140a139019dc71540000000000",
|
||||
"7400000002631e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000": "00000000ffffffff40420f00000000000014f4a5f44033305254c96ee91dd253f903af5944aa0000000000",
|
||||
"7400000002632ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "000000003800000028320f00000000000014fb392be3e9bd032482a3b24ec077f27c4fcf00b20000000001",
|
||||
"7400000002633cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000": "00000000ffffffff40420f000000000000145493e23f073a3dba4442e529038ffd00c3cb72700000000000",
|
||||
"740000000263458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "000000003300000040420f00000000000014bd835f1fd372fcc462a59b7e088dcaf1bdc8ca540000000100",
|
||||
"7400000002634eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000": "00000000ffffffff40420f00000000000014a956453db168abea91324667b05b286fa5c03bd50000000000",
|
||||
"74000000026353956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000": "00000000ffffffff40420f000000000000141ec0760b28899bb7b5ef6c617915c3db1d7324580000000000",
|
||||
"740000000263554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "000000003300000040420f00000000000014dbba0b358b90b46b04a86b01a46e0b844ea7c3680000000100",
|
||||
"7400000002635dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000001": "0000000041000000a8230c000000000000142e9187f08252e4be2044f7a41cd25ceb7ae96d460000000001",
|
||||
"74000000026363864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000001": "0000000047000000a4ae020000000000001432aa9c919d911fed1b053a452dcbf3704a759e940000000001",
|
||||
"740000000263683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "00000000ffffffff28320f00000000000014bfe57d3d51c357a99aa9c84195fe8e000273399a0000000001",
|
||||
"74000000026386387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000": "00000000ffffffff40420f00000000000014ce48fa97354847c1f35d85763e776bd9e372ec240000000000",
|
||||
"7400000002638b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000": "00000000ffffffff40420f0000000000001468a4797e126034fa8d58cdb8f709d61bda3fd7660000000000",
|
||||
"7400000002638eba67a23de92df548ecbb4f40be648bbf870a1de67191ad0b576c1fab59ea8b00000000": "000000003300000040420f00000000000014093a264c767174696332bf57a93ffceaabe23bde0000000000",
|
||||
"740000000263952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "000000003700000028320f00000000000014b44aae19323f53e63e5cd184dba19a71616808150000000001",
|
||||
"740000000263a7d8e41fbc89aacc349effd58b140f08b84ab4d33775bb967a678e8bf4d8c00a00000000": "000000003600000040420f000000000000147ed369a0353f353c38c282be4b153f1f883b04de0000010000",
|
||||
"740000000263ad6c09f1d7c9eacd449af12280c6ac397061a27bed995bc5dfe7b24ce2c49e3a00000000": "000000003300000040420f0000000000001478d8fc4cbe36b0fde3b3d337cebd4520dae1e4c60000000000",
|
||||
"740000000263b37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000": "00000000ffffffff40420f000000000000148c446d6b2942f2ed67b8e78c64a0a84cfa079f920000000000",
|
||||
"740000000263bf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000": "00000000ffffffff40420f000000000000141e3feab7c10d898c5762226c1a3d6969f470a89e0000000000",
|
||||
"740000000263d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000000": "0000000052000000102700000000000000146ff78bc8fc05796fca3c60163568ff5c9dd22acc06042075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a000000010020ae3895cf597eff05b19e02a70ceeeecb9dc72dbfe6504a50e9343a72f06a87c5000001",
|
||||
"740000000263d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000001": "00000000520000006c16000000000000001463ee2af024ea3dd2411e4de306e7f13fb26659df0000000001",
|
||||
"740000000263d949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000": "00000000ffffffff40420f0000000000001454b98d922ac0a29f3dd4a71280bea1aa35297c130000000000",
|
||||
"740000000263db3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000": "00000000ffffffff40420f00000000000014a7f6ac09b90a612149c8853772515fcc849361520000000000",
|
||||
"740000000263db40da9ef39f8b52f0cfc84fe1bd4320f9dcab3f0c7425af1e505cfd59ecedbb00000000": "000000003300000040420f000000000000143229a7234fc49dbd38ea2f203e01e4a7f15c4bd40000000000",
|
||||
"740000000263ddef667255e254fc9d885ee04fe4852dba08d4b7ba84e86562afe6351d62098000000000": "000000003300000040420f0000000000001483bd567dae4fb1abf5f6c53379e23c87ec036e770000000000",
|
||||
"740000000263e674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000": "00000000ffffffff40420f0000000000001419703fb89be3e452f3cf0541d02f6118cda5eced0000000000",
|
||||
"740000000263ee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000": "00000000ffffffff40420f000000000000142965055c105f10931d54d690ae8d5aca7aa5434d0000000000",
|
||||
"740000000263ee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000": "00000000ffffffff40420f000000000000140b2909a9cc84726cb1f01c32e45288f5b49349ad0000000000",
|
||||
"740000000263fa72230236318c48e79926683a93baa4ccf09e856a78dfef9e1bf20f9b947dbe00000000": "000000003300000040420f00000000000014784cc5364704372f14e7b68600aafbb2cd4aea390000000000",
|
||||
"740000000263fc7c46c4caa0d66e55e2b70c82fe65a68fef5fabfb5c903cc264afc68ea6b7bd00000000": "000000003300000040420f000000000000149b18d692909cfbd2b7d9342d41932ed4e5a7e7930000000000",
|
||||
"7400000002642ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000": "000000003300000040420f0000000000001446e641691144a03add0db507a26404313606782e000000",
|
||||
"7400000002642ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000001": "000000003300000040420f0000000000001449fec1e6fcbd2ccc2597e1b12986fc6ec8e64c07000000",
|
||||
"7400000002645dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000000": "000000003300000040420f000000000000149c1222d5dd6094fb7ee38bdb79e5d8eb7bdfd37b000000",
|
||||
"74000000026463864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000000": "0000000041000000400d03000000000000146ff78bc8fc05796fca3c60163568ff5c9dd22acc03042075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a0000000a746573746e616d652d3120909aec88e869c4d284606d5cc8054853bfa7c4e24c6041c4d8598f1b29bf282400",
|
||||
"740000000264683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000": "000000003300000040420f00000000000014bd835f1fd372fcc462a59b7e088dcaf1bdc8ca54000000",
|
||||
"740000000264683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000001": "000000003300000040420f00000000000014dbba0b358b90b46b04a86b01a46e0b844ea7c368000000",
|
||||
"740000000264952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000": "000000003300000040420f00000000000014852e98d8b8ab2946445f20b051e6b055b492c506000000",
|
||||
"740000000264952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000001": "000000003300000040420f00000000000014195fe465b1df13832ebe58e00aaefa05702ad39b000000",
|
||||
"740000000264c2d3e816222195ea0b04be6b7bb664e0b617f9c91a898682ea7214d5168c293700000000": "000000003300000040420f000000000000148e659d46d7ceb9464cc04f3aeb73bc2702c1e7a3000000",
|
||||
"740000000264c2d3e816222195ea0b04be6b7bb664e0b617f9c91a898682ea7214d5168c293700000001": "000000003300000040420f0000000000001424fd6822d0ad3e981832391cb004484cbcab15da000000",
|
||||
"740000000264d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000000": "0000000047000000204e00000000000000146ff78bc8fc05796fca3c60163568ff5c9dd22acc04032075c670d4f561a616175bb3e15282381fd8e08beafc861fe16568209c849e909d043a000000200cc30191083dcbf8e46ecaa8972af321588068915326f7f0851b2cee9872050500",
|
||||
"7400000002730cd502099a8d25b5b574bc381cf60b84f45163ccb32cb44d64d53375a2d8a97a00000000": "4eaedb2e215e18ac11b72228c0ebb6a4b056e9333e188b6a63f54d937d0a9ed000000000",
|
||||
"7400000002731012b7e8316dcc4c65e0b80bc8b964962088c25629483bc27a81fb072726432800000000": "952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e00000000",
|
||||
"7400000002731c820c68189da48293553b1cd7465cf7f75ac3bf2db9172f5f9ddebf3d10ad8500000000": "ee1f7646c89e2171132e0ee25021933051f7c12ecd704e8db06d9ffe4a8c8d5200000000",
|
||||
"74000000027325cf4fe01b9170e6641cd56aa6d2110ed7755c831648f6b209010a65cd17bfc900000000": "952e6e77a47172d5bf30feca405b92a500c7527c5b684eceb966b0df00e9335e01000000",
|
||||
"7400000002732c2a543be67720878d34455f26e69f830c741b4dfbf33cfcd4998b881a9c97bd00000000": "8b775b6dc4d7ac26c7f527fcc5b54a4d33f9aa8c75b6973b5e20da2cfd9e747700000000",
|
||||
"7400000002732dd1d165fc1bfa68e3bfec4e134141df8da1e37b9daa8436109cc0fb4b1805f600000000": "ee9f9b4d9d1b4ad59ae6ef2013d219f1036d2dcde4f5e6647713446ff829439a00000000",
|
||||
"740000000273458a24e82424fa1c01664265ac9af70cf065080937ee8e3bbe1744c1c8025db400000000": "683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331100000000",
|
||||
"7400000002734e26855b7519729fbbd5d525ded78b92308ebb2acc11f595793997d358db2dc900000000": "b37ce98e5ba10c3da50ecd80c5bd47fd930d8aeaadfa87470206a2a88908a3ac00000000",
|
||||
"740000000273554604839464bb5f55aa3a865c8ccda578647fadd974c9c4542335f0dfbeb3f700000000": "683182a33ad31deb2353a16675f80115242428cb8297e788098b49551dd6331101000000",
|
||||
"7400000002735dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000000": "63864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000000",
|
||||
"7400000002735f8e95f14b4655f2cd5f476b02390b083dd4208ccf1c73bc3bf3f7ae294d728b00000000": "2ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da00000000",
|
||||
"74000000027361886f1b56565fb2455e0b7d890eb674e01679dab480319320607e03c218912800000000": "d949b231cfa7f782ece5481a05973c95c6babe1ba7852aa0820e3f7eaec802ec00000000",
|
||||
"74000000027363849657fbafa56c0be2daa74185c51ff8323d1984b3d62becafb83a9f43866c00000000": "2ac00957d4ae882ff47c3af0ce782a0e8538593016af1e06d0190059dc72b7da01000000",
|
||||
"74000000027363864c0c27e6f92c7a37315ec0064c0f303626cd3c1348ba778545db9d57ebeb00000000": "d0012d0eee1130d4c3a8b407fb4ad08daad7b678b972e92cf3df85a2c72e579800000000",
|
||||
"74000000027365b569f91f6b28d447f12c5e093a68933f58ef7d60ab129fa236e61d406e5c8200000000": "c2d3e816222195ea0b04be6b7bb664e0b617f9c91a898682ea7214d5168c293700000000",
|
||||
"7400000002737250af6eb0c99bc0390f068687ee0f51e32340c16872da8c56427dd02c17aaa300000000": "86387e082d90691017258560c4e75fc4fe132cee113a6311dd4d3c40c6cc267e00000000",
|
||||
"7400000002737b1e678d97e5ac5cf768247a497c5b76b3e6e2eaf5dab87abef949e30385d19400000000": "c2d3e816222195ea0b04be6b7bb664e0b617f9c91a898682ea7214d5168c293701000000",
|
||||
"74000000027384440206ad559e1539aaf0d6b81f24c7bcca619032684024df17cb0b48a90f2d00000000": "5dcd9e696320eac817ead4c9ac30648611d291a9e0f92031d7504e7357efcba200000000",
|
||||
"74000000027386416be467af312440c129867613049d435ba837f71091aa56cef7829440f18e00000000": "1e31f64ba260272c9a6691f622d34ba236171b46cad336fedf66c79e92bdc9b000000000",
|
||||
"7400000002738a306eeb3501386cb9462e357d8574d0a51ab1a3904fcf6ad7fb516901bd90ff00000000": "bf725da6643ce3b35c92c1dafaf985158a7bc26b04e8b28a646aa4d5d04549a100000000",
|
||||
"7400000002738ab5e6fc1290ea699b3f2c98e99226212e6b7217a0f774e17697bf88d77fa2ff00000000": "53956ef1284e8b109112c30b5b248d0a7f3d38c31ebfb55aefbafe6956d3db9000000000",
|
||||
"7400000002738ef53020504301017d883fed0eed75001cb2242d219a372bb334895ef588789900000000": "1cfa3bc68820e8a3bcd36085cb3cf389c00939c92b5700455eedb91dfb2eb9a100000000",
|
||||
"740000000273d007a06dd5e690763c223421b6126ba058d04216a3a02e4c164348513b6495ea00000000": "3cde6d7e145fe413d71d7d5d06a0b5bfba09e76668c19357d2dac07b027fc7af00000000",
|
||||
"740000000273dfc49e74ea08407f6722adf59ca4e8c782ee0183beac232e02675863fc5fbbad00000000": "e674dc40410a1edac73a3bdae5605acc25b198b77b26c47de9b8867700fd0b8a00000000",
|
||||
"740000000273f8237a38c8e06c92a717eee4301299d785dd32765027b2fe30c683721c97379e00000000": "db3ab3342cf4b4ad2bd8ef7b944330e54595ecff2e39e60e78f7939a992c012900000000",
|
||||
"740000000273fc12d97b516e6e7fe68e9bbcb622784543b45d5b89c00d720115d1e697eca4f500000000": "1ad7aff76118c5ea7ac811195ba9c12a25170adfc879f7233bf031876c0b8bb400000000"
|
||||
}
|
||||
}
|
||||
405
test/mtx-test.js
405
test/mtx-test.js
|
|
@ -1,13 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('bsert');
|
||||
const random = require('bcrypto/lib/random');
|
||||
const CoinView = require('../lib/coins/coinview');
|
||||
const WalletCoinView = require('../lib/wallet/walletcoinview');
|
||||
const Coin = require('../lib/primitives/coin');
|
||||
const MTX = require('../lib/primitives/mtx');
|
||||
const Path = require('../lib/wallet/path');
|
||||
const MemWallet = require('./util/memwallet');
|
||||
const primutils = require('./util/primitives');
|
||||
const {randomP2PKAddress, makeCoin} = primutils;
|
||||
|
||||
const mtx1json = require('./data/mtx1.json');
|
||||
const mtx2json = require('./data/mtx2.json');
|
||||
|
|
@ -138,16 +138,318 @@ describe('MTX', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Fund', function() {
|
||||
describe('Fund with in memory coin selectors', function() {
|
||||
const createCoins = (values) => {
|
||||
return values.map(value => makeCoin({ value }));
|
||||
};
|
||||
|
||||
it('should fund with sorted values', async () => {
|
||||
const coins = createCoins([1e6, 2e6, 3e6, 4e6, 5e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 7e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 0
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs[0].value, 7e6);
|
||||
assert.strictEqual(mtx.outputs[1].value, 2e6);
|
||||
});
|
||||
|
||||
it('should fund with random selection', async () => {
|
||||
const coins = createCoins([1e6, 1e6, 1e6, 1e6, 1e6, 1e6, 1e6, 1e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 5e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 1e5,
|
||||
selection: 'random'
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 6);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs[0].value, 5e6);
|
||||
assert.strictEqual(mtx.getFee(), 1e5);
|
||||
assert.strictEqual(mtx.outputs[1].value, 9e5);
|
||||
});
|
||||
|
||||
it('should fund with all selection type', async () => {
|
||||
const coins = createCoins([1e6, 2e6, 3e6, 4e6, 5e6, 6e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 2e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 0,
|
||||
selection: 'all'
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 6);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs[0].value, 2e6);
|
||||
assert.strictEqual(mtx.getFee(), 0);
|
||||
assert.strictEqual(mtx.outputs[1].value, 19e6);
|
||||
});
|
||||
|
||||
it('should fund with age-based selection', async () => {
|
||||
const coins = [
|
||||
makeCoin({ value: 2e6, height: 100 }),
|
||||
makeCoin({ value: 3e6, height: 200 }),
|
||||
makeCoin({ value: 1e6, height: 50 })
|
||||
];
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 1e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 1e5,
|
||||
selection: 'age'
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.getFee(), 1e5);
|
||||
// Should select the oldest (lowest height) coins first
|
||||
assert.strictEqual(mtx.inputs[0].prevout.hash.equals(coins[2].hash), true);
|
||||
assert.strictEqual(mtx.inputs[1].prevout.hash.equals(coins[0].hash), true);
|
||||
});
|
||||
|
||||
it('should fund with value-based selection', async () => {
|
||||
const coins = [
|
||||
makeCoin({ value: 1e6 }),
|
||||
makeCoin({ value: 5e6 }),
|
||||
makeCoin({ value: 2e6 })
|
||||
];
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 4e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 1e5,
|
||||
selection: 'value'
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 1);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.getFee(), 1e5);
|
||||
// Should select the highest value coin first
|
||||
assert.strictEqual(mtx.inputs[0].prevout.hash.equals(coins[1].hash), true);
|
||||
});
|
||||
|
||||
it('should handle subtractFee option', async () => {
|
||||
const coins = createCoins([2e6, 3e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 5e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 1e5,
|
||||
subtractFee: true
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs.length, 1);
|
||||
assert.strictEqual(mtx.outputs[0].value, 4.9e6); // 5e6 - 1e5 = 4.9e6
|
||||
assert.strictEqual(mtx.getFee(), 1e5);
|
||||
});
|
||||
|
||||
it('should handle subtractIndex option', async () => {
|
||||
const coins = createCoins([3e6, 3e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 3e6);
|
||||
mtx.addOutput(randomP2PKAddress(), 3e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 2e5,
|
||||
subtractFee: true,
|
||||
subtractIndex: 1
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs[0].value, 3e6);
|
||||
assert.strictEqual(mtx.outputs[1].value, 2.8e6); // 3e6 - 2e5 = 2.8e6
|
||||
assert.strictEqual(mtx.getFee(), 2e5);
|
||||
});
|
||||
|
||||
it('should throw with insufficient funds', async () => {
|
||||
const coins = createCoins([1e6, 1e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 5e6);
|
||||
|
||||
let err;
|
||||
try {
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 0
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
assert(err);
|
||||
assert.strictEqual(err.message.includes('Not enough funds'), true);
|
||||
});
|
||||
|
||||
it('should throw when fee is too high', async () => {
|
||||
const coins = createCoins([1e6, 1e6, 1e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 2e6);
|
||||
|
||||
let err;
|
||||
try {
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
rate: 1e6, // Extremely high fee rate
|
||||
maxFee: 1e5 // But with a low maxFee
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
assert(err);
|
||||
assert.strictEqual(err.message.includes('Fee is too high'), true);
|
||||
});
|
||||
|
||||
it('should handle dust change', async () => {
|
||||
const coins = createCoins([1e6, 1e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 1.999e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 1e3
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs.length, 1);
|
||||
assert.strictEqual(mtx.getFee(), 1e3);
|
||||
assert.strictEqual(mtx.changeIndex, -1);
|
||||
});
|
||||
|
||||
it('should fund with exact amount needed', async () => {
|
||||
const coins = createCoins([1e6, 2e6, 3e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 3e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 0
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 1);
|
||||
assert.strictEqual(mtx.outputs.length, 1);
|
||||
assert.strictEqual(mtx.outputs[0].value, 3e6);
|
||||
assert.strictEqual(mtx.getFee(), 0);
|
||||
assert.strictEqual(mtx.changeIndex, -1);
|
||||
});
|
||||
|
||||
it('should add coin based on minimum required', async () => {
|
||||
const wallet = new MemWallet();
|
||||
const coins = [
|
||||
makeCoin({ address: wallet.getAddress(), value: 1e5 }),
|
||||
makeCoin({ address: wallet.getAddress(), value: 2e5 }),
|
||||
makeCoin({ address: wallet.getAddress(), value: 5e5 }),
|
||||
makeCoin({ address: wallet.getAddress(), value: 1e6 }),
|
||||
makeCoin({ address: wallet.getAddress(), value: 2e6 })
|
||||
];
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 1.5e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: wallet.getChange(),
|
||||
hardFee: 1e4
|
||||
});
|
||||
|
||||
// Should select the 2e6 coin (largest value first selection)
|
||||
assert.strictEqual(mtx.inputs.length, 1);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs[0].value, 1.5e6);
|
||||
assert.strictEqual(mtx.outputs[1].value, 2e6 - 1.5e6 - 1e4);
|
||||
assert.bufferEqual(mtx.inputs[0].prevout.hash, coins[4].hash);
|
||||
});
|
||||
|
||||
it('should combine multiple coins when necessary', async () => {
|
||||
const coins = createCoins([1e5, 2e5, 3e5, 4e5, 5e5]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 1e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 5e4
|
||||
});
|
||||
|
||||
// Should need to combine multiple coins to reach 1e6 + 5e4
|
||||
assert.ok(mtx.inputs.length > 1);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs[0].value, 1e6);
|
||||
assert.strictEqual(mtx.getFee(), 5e4);
|
||||
});
|
||||
|
||||
it('should correctly set changeIndex', async () => {
|
||||
const coins = createCoins([5e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 2e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
hardFee: 1e5
|
||||
});
|
||||
|
||||
assert.strictEqual(mtx.inputs.length, 1);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.strictEqual(mtx.changeIndex, 1);
|
||||
assert.strictEqual(mtx.outputs[1].value, 2.9e6); // 5e6 - 2e6 - 1e5 = 2.9e6
|
||||
});
|
||||
|
||||
it('should handle fee rates properly', async () => {
|
||||
const coins = createCoins([1e6, 2e6, 3e6]);
|
||||
|
||||
const mtx = new MTX();
|
||||
mtx.addOutput(randomP2PKAddress(), 4e6);
|
||||
|
||||
await mtx.fund(coins, {
|
||||
changeAddress: randomP2PKAddress(),
|
||||
rate: 5000 // dollarydoos per kb
|
||||
});
|
||||
|
||||
// The exact fee will depend on the estimated tx size
|
||||
assert.strictEqual(mtx.inputs.length, 2);
|
||||
assert.strictEqual(mtx.outputs.length, 2);
|
||||
assert.ok(mtx.getFee() > 0);
|
||||
assert.ok(mtx.getFee() < 1e5); // Reasonable upper bound for test
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fund preferred & existing', function() {
|
||||
const wallet1 = new MemWallet();
|
||||
const wallet2 = new MemWallet();
|
||||
|
||||
const coins1 = [
|
||||
dummyCoin(wallet1.getAddress(), 1000000),
|
||||
dummyCoin(wallet1.getAddress(), 1000000),
|
||||
dummyCoin(wallet1.getAddress(), 1000000),
|
||||
dummyCoin(wallet1.getAddress(), 1000000),
|
||||
dummyCoin(wallet1.getAddress(), 1000000)
|
||||
makeCoin({ address: wallet1.getAddress(), value: 1000000 }),
|
||||
makeCoin({ address: wallet1.getAddress(), value: 1000000 }),
|
||||
makeCoin({ address: wallet1.getAddress(), value: 1000000 }),
|
||||
makeCoin({ address: wallet1.getAddress(), value: 1000000 }),
|
||||
makeCoin({ address: wallet1.getAddress(), value: 1000000 })
|
||||
];
|
||||
|
||||
const last1 = coins1[coins1.length - 1];
|
||||
|
|
@ -239,7 +541,10 @@ describe('MTX', function() {
|
|||
|
||||
it('should fund with preferred inputs - view', async () => {
|
||||
const mtx = new MTX();
|
||||
const coin = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
|
||||
mtx.addOutput(wallet2.getAddress(), 1500000);
|
||||
mtx.view.addCoin(coin);
|
||||
|
|
@ -266,7 +571,10 @@ describe('MTX', function() {
|
|||
|
||||
it('should fund with preferred inputs - coins && view', async () => {
|
||||
const mtx = new MTX();
|
||||
const viewCoin = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const viewCoin = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const lastCoin = last1;
|
||||
|
||||
mtx.addOutput(wallet2.getAddress(), 1500000);
|
||||
|
|
@ -304,7 +612,10 @@ describe('MTX', function() {
|
|||
|
||||
it('should not fund with preferred inputs and no coin info', async () => {
|
||||
const mtx = new MTX();
|
||||
const coin = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
|
||||
mtx.addOutput(wallet2.getAddress(), 1500000);
|
||||
|
||||
|
|
@ -357,7 +668,10 @@ describe('MTX', function() {
|
|||
|
||||
it('should fund with existing inputs view - view', async () => {
|
||||
const mtx = new MTX();
|
||||
const coin = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
|
||||
mtx.addInput({
|
||||
prevout: {
|
||||
|
|
@ -388,7 +702,10 @@ describe('MTX', function() {
|
|||
|
||||
it('should fund with existing inputs view - coins && view', async () => {
|
||||
const mtx = new MTX();
|
||||
const viewCoin = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const viewCoin = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const lastCoin = last1;
|
||||
|
||||
mtx.addInput({
|
||||
|
|
@ -434,7 +751,10 @@ describe('MTX', function() {
|
|||
|
||||
it('should not fund with existing inputs and no coin info', async () => {
|
||||
const mtx = new MTX();
|
||||
const coin = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
|
||||
mtx.addInput({
|
||||
prevout: {
|
||||
|
|
@ -502,8 +822,14 @@ describe('MTX', function() {
|
|||
|
||||
it('should fund with preferred & existing inputs - view', async () => {
|
||||
const mtx = new MTX();
|
||||
const coin1 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin2 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin1 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coin2 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
|
||||
mtx.addInput({
|
||||
prevout: {
|
||||
|
|
@ -546,11 +872,17 @@ describe('MTX', function() {
|
|||
it('should fund with preferred & existing inputs', async () => {
|
||||
const mtx = new MTX();
|
||||
// existing
|
||||
const coin1 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin1 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coinLast1 = last1;
|
||||
|
||||
// preferred
|
||||
const coin2 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin2 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coinLast2 = last2;
|
||||
|
||||
mtx.addInput({
|
||||
|
|
@ -620,11 +952,17 @@ describe('MTX', function() {
|
|||
it('should not fund with missing coin info (both)', async () => {
|
||||
const mtx = new MTX();
|
||||
// existing
|
||||
const coin1 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin1 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coinLast1 = last1;
|
||||
|
||||
// preferred
|
||||
const coin2 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin2 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coinLast2 = last2;
|
||||
|
||||
mtx.addInput({
|
||||
|
|
@ -666,11 +1004,17 @@ describe('MTX', function() {
|
|||
it('should not fund with missing coin info(only existing)', async () => {
|
||||
const mtx = new MTX();
|
||||
// existing
|
||||
const coin1 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin1 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coinLast1 = last1;
|
||||
|
||||
// preferred
|
||||
const coin2 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin2 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coinLast2 = last2;
|
||||
|
||||
mtx.addInput({
|
||||
|
|
@ -713,11 +1057,17 @@ describe('MTX', function() {
|
|||
it('should not fund with missing coin info(only preferred)', async () => {
|
||||
const mtx = new MTX();
|
||||
// existing
|
||||
const coin1 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin1 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coinLast1 = last1;
|
||||
|
||||
// preferred
|
||||
const coin2 = dummyCoin(wallet1.getAddress(), 1000000);
|
||||
const coin2 = makeCoin({
|
||||
address: wallet1.getAddress(),
|
||||
value: 1000000
|
||||
});
|
||||
const coinLast2 = last2;
|
||||
|
||||
mtx.addInput({
|
||||
|
|
@ -758,10 +1108,3 @@ describe('MTX', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
function dummyCoin(address, value) {
|
||||
const hash = random.randomBytes(32);
|
||||
const index = 0;
|
||||
|
||||
return new Coin({address, value, hash, index});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ const consensus = require('../../lib/protocol/consensus');
|
|||
const BlockTemplate = require('../../lib/mining/template');
|
||||
const bdb = require('bdb');
|
||||
|
||||
/** @typedef {import('../../lib/blockchain/chain')} Chain */
|
||||
/** @typedef {import('../../lib/mining/miner')} Miner */
|
||||
/** @typedef {import('../../lib/primitives/address')} Address */
|
||||
|
||||
let Migrator = class {};
|
||||
|
||||
try {
|
||||
|
|
@ -190,7 +194,7 @@ exports.mockLayout = mockLayout;
|
|||
exports.oldMockLayout = oldMockLayout;
|
||||
exports.DB_FLAG_ERROR = DB_FLAG_ERROR;
|
||||
|
||||
exports.migrationError = (migrations, ids, flagError) => {
|
||||
exports.migrationError = function migrationError(migrations, ids, flagError) {
|
||||
let error = 'Database needs migration(s):\n';
|
||||
|
||||
for (const id of ids) {
|
||||
|
|
@ -207,8 +211,12 @@ exports.prefix2hex = function prefix2hex(prefix) {
|
|||
return Buffer.from(prefix, 'ascii').toString('hex');
|
||||
};
|
||||
|
||||
exports.dumpDB = async (db, prefixes) => {
|
||||
exports.dumpDB = async function dumpDB(db, prefixes) {
|
||||
const data = await db.dump();
|
||||
return exports.filteredObject(data, prefixes);
|
||||
};
|
||||
|
||||
exports.filteredObject = function filteredObject(data, prefixes) {
|
||||
const filtered = {};
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
|
|
@ -223,7 +231,7 @@ exports.dumpDB = async (db, prefixes) => {
|
|||
return filtered;
|
||||
};
|
||||
|
||||
exports.dumpChainDB = async (chaindb, prefixes) => {
|
||||
exports.dumpChainDB = async function dumpChainDB(chaindb, prefixes) {
|
||||
return exports.dumpDB(chaindb.db, prefixes);
|
||||
};
|
||||
|
||||
|
|
@ -238,7 +246,7 @@ exports.dumpChainDB = async (chaindb, prefixes) => {
|
|||
* @returns {Promise<String[]>} - errors.
|
||||
*/
|
||||
|
||||
exports.checkEntries = async (ldb, options) => {
|
||||
exports.checkEntries = async function checkEntries(ldb, options) {
|
||||
const errors = [];
|
||||
|
||||
options.before = options.before || {};
|
||||
|
|
@ -296,7 +304,64 @@ exports.checkEntries = async (ldb, options) => {
|
|||
return errors;
|
||||
};
|
||||
|
||||
exports.fillEntries = async (ldb, data) => {
|
||||
/**
|
||||
* @param {bdb.DB} ldb
|
||||
* @param {String[]} prefixes
|
||||
* @param {Object} options
|
||||
* @param {Object} options.after - key value pairs to check.
|
||||
* @param {Boolean} options.throw - throw on error.
|
||||
* @param {Boolean} options.bail - bail on first error.
|
||||
* @param {Boolean} options.logErrors - log errors.
|
||||
* @returns {Promise<String[]>} - errors.
|
||||
*/
|
||||
|
||||
exports.checkExactEntries = async function checkExactEntries(ldb, prefixes, options) {
|
||||
const dumped = await exports.dumpDB(ldb, prefixes);
|
||||
const after = exports.filteredObject(options.after, prefixes);
|
||||
|
||||
const checks = new Set(Object.keys(after));
|
||||
const errors = [];
|
||||
|
||||
for (const [key, value] of Object.entries(dumped)) {
|
||||
if (errors.length > 0 && options.bail) {
|
||||
if (options.throw)
|
||||
throw new Error(errors[0]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!checks.has(key)) {
|
||||
errors.push(`Unexpected key found in db: ${key}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value !== after[key]) {
|
||||
errors.push(`Value for ${key}: ${value} does not match expected: ${after[key]}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
checks.delete(key);
|
||||
}
|
||||
|
||||
if (checks.size > 0) {
|
||||
for (const key of checks) {
|
||||
errors.push(`Expected key ${key} not found in db.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.logErrors && errors.length !== 0) {
|
||||
console.error(
|
||||
JSON.stringify(errors, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
if (errors.length > 0 && options.throw)
|
||||
throw new Error(`Check exact entries failed with ${errors.length} errors.`);
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
exports.fillEntries = async function fillEntries(ldb, data) {
|
||||
const batch = await ldb.batch();
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
|
|
@ -309,7 +374,7 @@ exports.fillEntries = async (ldb, data) => {
|
|||
await batch.write();
|
||||
};
|
||||
|
||||
exports.writeVersion = (b, key, name, version) => {
|
||||
exports.writeVersion = function writeVersion(b, key, name, version) {
|
||||
const value = Buffer.alloc(name.length + 4);
|
||||
|
||||
value.write(name, 0, 'ascii');
|
||||
|
|
@ -318,7 +383,7 @@ exports.writeVersion = (b, key, name, version) => {
|
|||
b.put(key, value);
|
||||
};
|
||||
|
||||
exports.getVersion = (data, name) => {
|
||||
exports.getVersion = function getVersion(data, name) {
|
||||
const error = 'version mismatch';
|
||||
|
||||
if (data.length !== name.length + 4)
|
||||
|
|
@ -330,7 +395,7 @@ exports.getVersion = (data, name) => {
|
|||
return data.readUInt32LE(name.length);
|
||||
};
|
||||
|
||||
exports.checkVersion = async (ldb, versionDBKey, expectedVersion) => {
|
||||
exports.checkVersion = async function checkVersion(ldb, versionDBKey, expectedVersion) {
|
||||
const data = await ldb.get(versionDBKey);
|
||||
const version = exports.getVersion(data, 'wallet');
|
||||
|
||||
|
|
@ -352,7 +417,7 @@ const getBlockTime = height => REGTEST_TIME + (height * 10 * 60);
|
|||
* @returns {BlockTemplate}
|
||||
*/
|
||||
|
||||
exports.createBlock = async (options) => {
|
||||
exports.createBlock = async function createBlock(options) {
|
||||
const {
|
||||
chain,
|
||||
miner,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
const assert = require('bsert');
|
||||
const {forEventCondition} = require('./common');
|
||||
|
||||
exports.generateInitialBlocks = async (options) => {
|
||||
exports.generateInitialBlocks = async function generateInitialBlocks(options) {
|
||||
const {
|
||||
nodeCtx,
|
||||
coinbase,
|
||||
|
|
|
|||
217
test/util/primitives.js
Normal file
217
test/util/primitives.js
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('bsert');
|
||||
const blake2b = require('bcrypto/lib/blake2b');
|
||||
const random = require('bcrypto/lib/random');
|
||||
const rules = require('../../lib/covenants/rules');
|
||||
const Input = require('../../lib/primitives/input');
|
||||
const Address = require('../../lib/primitives/address');
|
||||
const Output = require('../../lib/primitives/output');
|
||||
const Outpoint = require('../../lib/primitives/outpoint');
|
||||
const Coin = require('../../lib/primitives/coin');
|
||||
const Covenant = require('../../lib/primitives/covenant');
|
||||
|
||||
/** @typedef {import('../../lib/types').Hash} Hash */
|
||||
|
||||
exports.coinbaseInput = function coinbaseInput() {
|
||||
return Input.fromOutpoint(new Outpoint());
|
||||
};
|
||||
|
||||
exports.dummyInput = function dummyInput () {
|
||||
const hash = random.randomBytes(32);
|
||||
return Input.fromOutpoint(new Outpoint(hash, 0));
|
||||
};
|
||||
|
||||
exports.deterministicInput = function deterministicInput(id) {
|
||||
const hash = blake2b.digest(fromU32(id));
|
||||
return Input.fromOutpoint(new Outpoint(hash, 0));
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} OutputOptions
|
||||
* @property {Number} value
|
||||
* @property {Address} [address]
|
||||
* @property {CovenantOptions} [covenant]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {OutputOptions} options
|
||||
* @returns {Output}
|
||||
*/
|
||||
|
||||
exports.makeOutput = function makeOutput(options) {
|
||||
const address = options.address || exports.randomP2PKAddress();
|
||||
const output = new Output();
|
||||
output.address = address;
|
||||
output.value = options.value;
|
||||
|
||||
if (options.covenant)
|
||||
output.covenant = exports.makeCovenant(options.covenant);
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} CovenantOptions
|
||||
* @property {String} [name]
|
||||
* @property {Hash} [nameHash]
|
||||
* @property {Covenant.types} [type=Covenant.types.NONE]
|
||||
* @property {Number} [height]
|
||||
* @property {Array} [args] - leftover args for the covenant except
|
||||
* for nameHash, name and height.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {CovenantOptions} options
|
||||
* @returns {Covenant}
|
||||
*/
|
||||
|
||||
exports.makeCovenant = function makeCovenant(options) {
|
||||
const covenant = new Covenant();
|
||||
covenant.type = options.type || Covenant.types.NONE;
|
||||
|
||||
const args = options.args || [];
|
||||
const height = options.height || 0;
|
||||
let nameHash = options.nameHash;
|
||||
let name = options.name;
|
||||
|
||||
if (name) {
|
||||
nameHash = rules.hashName(name);
|
||||
} else if (!nameHash) {
|
||||
name = exports.randomName(30);
|
||||
nameHash = rules.hashName(name);
|
||||
}
|
||||
|
||||
switch (covenant.type) {
|
||||
case Covenant.types.NONE:
|
||||
break;
|
||||
case Covenant.types.OPEN: {
|
||||
assert(args.length === 0, 'Pass `options.name` instead.');
|
||||
const rawName = Buffer.from(name, 'ascii');
|
||||
covenant.setOpen(nameHash, rawName);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.BID: {
|
||||
assert(args.length <= 1, 'Pass [blind?] instead.');
|
||||
const blind = args[0] || random.randomBytes(32);
|
||||
const rawName = Buffer.from(name, 'ascii');
|
||||
covenant.setBid(nameHash, height, rawName, blind);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.REVEAL: {
|
||||
assert(args.length <= 1, 'Pass [nonce?] instead.');
|
||||
const nonce = args[0] || random.randomBytes(32);
|
||||
covenant.setReveal(nameHash, height, nonce);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.REDEEM: {
|
||||
assert(args.length === 0, 'No args for redeem.');
|
||||
covenant.setRedeem(nameHash, height);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.REGISTER: {
|
||||
assert(args.length <= 2, 'Pass [record?, blockHash?] instead.');
|
||||
const record = args[0] || Buffer.alloc(0);
|
||||
const blockHash = args[1] || random.randomBytes(32);
|
||||
covenant.setRegister(nameHash, height, record, blockHash);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.UPDATE: {
|
||||
assert(args.length <= 1, 'Pass [resource?] instead.');
|
||||
const resource = args[0] || Buffer.alloc(0);
|
||||
covenant.setUpdate(nameHash, height, resource);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.RENEW: {
|
||||
assert(args.length <= 1, 'Pass [blockHash?] instead.');
|
||||
const blockHash = args[0] || random.randomBytes(32);
|
||||
covenant.setRenew(nameHash, height, blockHash);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.TRANSFER: {
|
||||
assert(args.length <= 1, 'Pass [address?] instead.');
|
||||
const address = args[0] || exports.randomP2PKAddress();
|
||||
covenant.setTransfer(nameHash, height, address);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.FINALIZE: {
|
||||
assert(args.length <= 4, 'Pass [flags?, claimed?, renewal?, blockHash?] instead.');
|
||||
const rawName = Buffer.from(name, 'ascii');
|
||||
const flags = args[0] || 0;
|
||||
const claimed = args[1] || 0;
|
||||
const renewal = args[2] || 0;
|
||||
const blockHash = args[3] || random.randomBytes(32);
|
||||
|
||||
covenant.setFinalize(
|
||||
nameHash,
|
||||
height,
|
||||
rawName,
|
||||
flags,
|
||||
claimed,
|
||||
renewal,
|
||||
blockHash
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Covenant.types.REVOKE: {
|
||||
assert(args.length === 0, 'No args for revoke.');
|
||||
covenant.setRevoke(nameHash, height);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Invalid covenant type ${covenant.type}.`);
|
||||
}
|
||||
|
||||
return covenant;
|
||||
};
|
||||
|
||||
exports.randomP2PKAddress = function randomP2PKAddress() {
|
||||
const key = random.randomBytes(33);
|
||||
return Address.fromPubkey(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} CoinOptions
|
||||
* @param {String} [options.version=1]
|
||||
* @param {String} [options.height=-1]
|
||||
* @param {String} [options.value=0]
|
||||
* @param {String} [options.address]
|
||||
* @param {Object} [options.covenant]
|
||||
* @param {Boolean} [options.coinbase=false]
|
||||
* @param {Buffer} [options.hash]
|
||||
* @param {Number} [options.index=0]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {CoinOptions} options
|
||||
* @returns {Coin}
|
||||
*/
|
||||
|
||||
exports.makeCoin = function makeCoin(options) {
|
||||
return Coin.fromOptions({
|
||||
hash: options.hash || random.randomBytes(32),
|
||||
address: options.address || Address.fromPubkey(random.randomBytes(33)),
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
function fromU32(num) {
|
||||
const data = Buffer.allocUnsafe(4);
|
||||
data.writeUInt32LE(num, 0, true);
|
||||
return data;
|
||||
}
|
||||
|
||||
exports.randomName = function randomName(len) {
|
||||
assert((len >>> 0) === len);
|
||||
|
||||
let s = '';
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const n = Math.random() * (0x7b - 0x61) + 0x61;
|
||||
const c = Math.floor(n);
|
||||
|
||||
s += String.fromCharCode(c);
|
||||
}
|
||||
|
||||
return s;
|
||||
};
|
||||
|
|
@ -2,11 +2,16 @@
|
|||
|
||||
const assert = require('bsert');
|
||||
const blake2b = require('bcrypto/lib/blake2b');
|
||||
const random = require('bcrypto/lib/random');
|
||||
const ChainEntry = require('../../lib/blockchain/chainentry');
|
||||
const Input = require('../../lib/primitives/input');
|
||||
const Outpoint = require('../../lib/primitives/outpoint');
|
||||
const MTX = require('../../lib/primitives/mtx');
|
||||
const {ZERO_HASH} = require('../../lib/protocol/consensus');
|
||||
const primutils = require('./primitives');
|
||||
const {coinbaseInput, makeOutput} = primutils;
|
||||
|
||||
/** @typedef {import('../../lib/types').Amount} Amount */
|
||||
/** @typedef {import('../../lib/covenants/rules').types} covenantTypes */
|
||||
/** @typedef {import('../../lib/primitives/output')} Output */
|
||||
/** @typedef {import('../../lib/wallet/wallet')} Wallet */
|
||||
|
||||
const walletUtils = exports;
|
||||
|
||||
|
|
@ -35,16 +40,6 @@ walletUtils.fakeBlock = (height, prevSeed = 0, seed = prevSeed) => {
|
|||
};
|
||||
};
|
||||
|
||||
walletUtils.dummyInput = () => {
|
||||
const hash = random.randomBytes(32);
|
||||
return Input.fromOutpoint(new Outpoint(hash, 0));
|
||||
};
|
||||
|
||||
walletUtils.deterministicInput = (id) => {
|
||||
const hash = blake2b.digest(fromU32(id));
|
||||
return Input.fromOutpoint(new Outpoint(hash, 0));
|
||||
};
|
||||
|
||||
walletUtils.nextBlock = (wdb, prevSeed = 0, seed = prevSeed) => {
|
||||
return walletUtils.fakeBlock(wdb.state.height + 1, prevSeed, seed);
|
||||
};
|
||||
|
|
@ -88,3 +83,144 @@ walletUtils.dumpWDB = async (wdb, prefixes) => {
|
|||
|
||||
return filtered;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} OutputInfo
|
||||
* @property {String} [address]
|
||||
* @property {Number} [account=0] - address generation account.
|
||||
* @property {Amount} [value]
|
||||
* @property {covenantTypes} [covenant]
|
||||
* @property {Boolean} [coinbase=false]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Wallet} wallet
|
||||
* @param {primutils.OutputOptions} outputInfo
|
||||
* @param {Object} options
|
||||
* @param {Boolean} [options.createAddress=true] - create address if not provided.
|
||||
* @returns {Promise<Output>}
|
||||
*/
|
||||
|
||||
async function mkOutput(wallet, outputInfo, options = {}) {
|
||||
const info = { ...outputInfo };
|
||||
|
||||
const {
|
||||
createAddress = true
|
||||
} = options;
|
||||
|
||||
if (!info.address) {
|
||||
const account = outputInfo.account || 0;
|
||||
|
||||
if (createAddress) {
|
||||
const walletKey = await wallet.createReceive(account);
|
||||
info.address = walletKey.getAddress();
|
||||
} else {
|
||||
info.address = await wallet.receiveAddress(account);
|
||||
}
|
||||
}
|
||||
|
||||
return makeOutput(info);
|
||||
}
|
||||
|
||||
walletUtils.deterministicId = 0;
|
||||
|
||||
/**
|
||||
* Create Inbound TX Options
|
||||
* @typedef {Object} InboundTXOptions
|
||||
* @property {Boolean} [txPerOutput=true]
|
||||
* @property {Boolean} [createAddress=true]
|
||||
* @property {Boolean} [deterministicInput=false]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create funding MTXs for a wallet.
|
||||
* @param {Wallet} wallet
|
||||
* @param {OutputInfo[]} outputInfos
|
||||
* @param {InboundTXOptions} options
|
||||
* @returns {Promise<TX[]>}
|
||||
*/
|
||||
|
||||
walletUtils.createInboundTXs = async function createInboundTXs(wallet, outputInfos, options = {}) {
|
||||
assert(Array.isArray(outputInfos));
|
||||
|
||||
const {
|
||||
txPerOutput = true,
|
||||
createAddress = true
|
||||
} = options;
|
||||
|
||||
let hadCoinbase = false;
|
||||
|
||||
const txs = [];
|
||||
|
||||
let mtx = new MTX();
|
||||
|
||||
let getInput = primutils.dummyInput;
|
||||
|
||||
if (options.deterministicInput) {
|
||||
getInput = () => {
|
||||
const id = walletUtils.deterministicId++;
|
||||
return primutils.deterministicInput(id);
|
||||
};
|
||||
}
|
||||
|
||||
for (const info of outputInfos) {
|
||||
if (txPerOutput)
|
||||
mtx = new MTX();
|
||||
|
||||
if (info.coinbase && hadCoinbase)
|
||||
throw new Error('Coinbase already added.');
|
||||
|
||||
if (info.coinbase && !hadCoinbase) {
|
||||
if (!txPerOutput)
|
||||
hadCoinbase = true;
|
||||
mtx.addInput(coinbaseInput());
|
||||
} else if (!hadCoinbase) {
|
||||
mtx.addInput(getInput());
|
||||
}
|
||||
|
||||
const output = await mkOutput(wallet, info, { createAddress });
|
||||
mtx.addOutput(output);
|
||||
|
||||
if (output.covenant.isLinked())
|
||||
mtx.addInput(getInput());
|
||||
|
||||
if (txPerOutput)
|
||||
txs.push(mtx.toTX());
|
||||
}
|
||||
|
||||
if (!txPerOutput)
|
||||
txs.push(mtx.toTX());
|
||||
|
||||
return txs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fund wallet options
|
||||
* @typedef {Object} FundOptions
|
||||
* @property {Boolean} [txPerOutput=true]
|
||||
* @property {Boolean} [createAddress=true]
|
||||
* @property {Boolean} [blockPerTX=false]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Wallet} wallet
|
||||
* @param {OutputInfo[]} outputInfos
|
||||
* @param {FundOptions} options
|
||||
* @returns {Promise<TX[]>}
|
||||
*/
|
||||
|
||||
walletUtils.fundWallet = async function fundWallet(wallet, outputInfos, options = {}) {
|
||||
const txs = await walletUtils.createInboundTXs(wallet, outputInfos, options);
|
||||
|
||||
if (!options.blockPerTX) {
|
||||
await wallet.wdb.addBlock(walletUtils.nextBlock(wallet.wdb), txs);
|
||||
return txs;
|
||||
}
|
||||
|
||||
for (const tx of txs) {
|
||||
await wallet.wdb.addTX(tx);
|
||||
await wallet.wdb.addBlock(walletUtils.nextBlock(wallet.wdb), [tx]);
|
||||
}
|
||||
|
||||
return txs;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ const MTX = require('../lib/primitives/mtx');
|
|||
const WorkerPool = require('../lib/workers/workerpool');
|
||||
const WalletDB = require('../lib/wallet/walletdb');
|
||||
const wutils = require('./util/wallet');
|
||||
const {
|
||||
dummyInput,
|
||||
nextEntry
|
||||
} = wutils;
|
||||
const {nextEntry} = wutils;
|
||||
const {dummyInput} = require('./util/primitives');
|
||||
|
||||
const enabled = true;
|
||||
const size = 2;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -25,6 +25,7 @@ const {
|
|||
getVersion,
|
||||
checkVersion,
|
||||
checkEntries,
|
||||
checkExactEntries,
|
||||
fillEntries
|
||||
} = migutils;
|
||||
const {rimraf, testdir} = require('./util/common');
|
||||
|
|
@ -957,7 +958,6 @@ describe('Wallet Migrations', function() {
|
|||
describe(`TX Count and time indexing migration (integration ${i})`, function() {
|
||||
const location = testdir('wallet-tx-count-time');
|
||||
const migrationsBAK = WalletMigrator.migrations;
|
||||
// const data = require('./data/migrations/wallet-5-pagination.json');
|
||||
const Migration = WalletMigrator.MigrateTXCountTimeIndex;
|
||||
const layout = Migration.layout();
|
||||
|
||||
|
|
@ -1107,4 +1107,144 @@ describe('Wallet Migrations', function() {
|
|||
await walletDB.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Migrate coin selection (data)', function() {
|
||||
const location = testdir('wallet-migrate-coin-selection');
|
||||
const data = require('./data/migrations/wallet-7-coinselector.json');
|
||||
const migrationsBAK = WalletMigrator.migrations;
|
||||
const Migration = WalletMigrator.MigrateCoinSelection;
|
||||
|
||||
const walletOptions = {
|
||||
prefix: location,
|
||||
memory: false,
|
||||
network
|
||||
};
|
||||
|
||||
let walletDB, ldb;
|
||||
beforeEach(async () => {
|
||||
WalletMigrator.migrations = {};
|
||||
await fs.mkdirp(location);
|
||||
|
||||
walletDB = new WalletDB(walletOptions);
|
||||
ldb = walletDB.db;
|
||||
|
||||
await walletDB.open();
|
||||
await fillEntries(walletDB.db, data.before);
|
||||
await walletDB.close();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
WalletMigrator.migrations = migrationsBAK;
|
||||
await rimraf(location);
|
||||
});
|
||||
|
||||
it('should migrate', async () => {
|
||||
WalletMigrator.migrations = {
|
||||
0: Migration
|
||||
};
|
||||
|
||||
walletDB.options.walletMigrate = 0;
|
||||
|
||||
await walletDB.open();
|
||||
|
||||
// Check that we have removed and added
|
||||
// the expected entries.
|
||||
await checkEntries(ldb, {
|
||||
before: data.before,
|
||||
after: data.after,
|
||||
throw: true
|
||||
});
|
||||
|
||||
// check that we have not created extra entries in the db
|
||||
// that is not present in the data dump.
|
||||
await checkExactEntries(ldb, data.prefixes, {
|
||||
after: data.after,
|
||||
throw: true
|
||||
});
|
||||
|
||||
await walletDB.close();
|
||||
});
|
||||
|
||||
it('should resume the progress of migration if interrupted', async () => {
|
||||
// patch the db buckets to throw after each write.
|
||||
const patchDB = () => {
|
||||
// throw after each bucket write.
|
||||
const ldbBucket = walletDB.db.bucket;
|
||||
|
||||
walletDB.db.bucket = (prefix) => {
|
||||
const bucket = ldbBucket.call(ldb, prefix);
|
||||
const bucketBatch = bucket.batch;
|
||||
|
||||
bucket.batch = () => {
|
||||
const batch = bucketBatch.call(bucket);
|
||||
const originalWrite = batch.write;
|
||||
|
||||
batch.write = async () => {
|
||||
await originalWrite.call(batch);
|
||||
throw new Error('Interrupt migration');
|
||||
};
|
||||
|
||||
return batch;
|
||||
};
|
||||
|
||||
return bucket;
|
||||
};
|
||||
|
||||
return () => {
|
||||
walletDB.db.bucket = ldbBucket;
|
||||
};
|
||||
};
|
||||
|
||||
WalletMigrator.migrations = {
|
||||
0: class extends Migration {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.batchSize = 10;
|
||||
}
|
||||
|
||||
async migrate(b, ctx) {
|
||||
const unpatch = patchDB();
|
||||
await super.migrate(b, ctx);
|
||||
unpatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await walletDB.db.open();
|
||||
|
||||
const migrator = new WalletMigrator({
|
||||
walletMigrate: 0,
|
||||
walletDB: walletDB,
|
||||
dbVersion: 5
|
||||
});
|
||||
|
||||
let err;
|
||||
|
||||
do {
|
||||
try {
|
||||
await migrator.migrate();
|
||||
err = null;
|
||||
} catch (e) {
|
||||
if (e.message !== 'Interrupt migration')
|
||||
throw e;
|
||||
|
||||
err = e;
|
||||
}
|
||||
} while (err);
|
||||
|
||||
await checkEntries(ldb, {
|
||||
before: data.before,
|
||||
after: data.after,
|
||||
throw: true
|
||||
});
|
||||
|
||||
await checkExactEntries(ldb, data.prefixes, {
|
||||
after: data.after,
|
||||
throw: true
|
||||
});
|
||||
|
||||
await walletDB.db.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ const WalletDB = require('../lib/wallet/walletdb');
|
|||
const consensus = require('../lib/protocol/consensus');
|
||||
const util = require('../lib/utils/util');
|
||||
const wutils = require('./util/wallet');
|
||||
const {
|
||||
dummyInput,
|
||||
nextEntry
|
||||
} = wutils;
|
||||
const {nextEntry} = wutils;
|
||||
const {dummyInput} = require('./util/primitives');
|
||||
|
||||
/** @typedef {import('../lib/wallet/wallet')} Wallet */
|
||||
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ const wutils = require('./util/wallet');
|
|||
const {ownership} = require('../lib/covenants/ownership');
|
||||
const {CachedStubResolver, STUB_SERVERS} = require('./util/stub');
|
||||
const {
|
||||
dummyInput,
|
||||
curBlock,
|
||||
nextBlock,
|
||||
curEntry,
|
||||
nextEntry
|
||||
} = wutils;
|
||||
const {dummyInput} = require('./util/primitives');
|
||||
|
||||
const KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt'
|
||||
+ 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ';
|
||||
|
|
@ -401,7 +401,7 @@ describe('Wallet', function() {
|
|||
assert.strictEqual(balanceBefore.tx, 2);
|
||||
assert.strictEqual(balanceBefore.coin, 2);
|
||||
|
||||
await wdb.removeBlock(block, [cbTX.toTX(), normalTX.toTX()]);
|
||||
await wdb.removeBlock(block);
|
||||
const pending = await wallet.getPending();
|
||||
|
||||
assert.strictEqual(pending.length, 1);
|
||||
|
|
@ -2224,23 +2224,16 @@ describe('Wallet', function() {
|
|||
// Store balance data before rescan to ensure rescan was complete
|
||||
let recipBalBefore, senderBalBefore;
|
||||
|
||||
// Hack required to focus test on txdb mechanics.
|
||||
// We don't otherwise need WalletDB or Blockchain
|
||||
// TODO: Remove this after #888 is merged.
|
||||
wdb.getRenewalBlock = () => {
|
||||
return network.genesis.hash;
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await wdb.open();
|
||||
await wdb.connect();
|
||||
wallet = await wdb.create();
|
||||
recip = await wdb.create();
|
||||
// rollout all names
|
||||
wdb.height = 52 * 144 * 7;
|
||||
network.names.noRollout = true;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
network.names.noRollout = false;
|
||||
await wdb.disconnect();
|
||||
await wdb.close();
|
||||
});
|
||||
|
|
@ -2610,21 +2603,15 @@ describe('Wallet', function() {
|
|||
let start;
|
||||
let wallet;
|
||||
|
||||
// Hack required to focus test on txdb mechanics.
|
||||
// We don't otherwise need WalletDB or Blockchain
|
||||
// TODO: Remove this after #888 is merged.
|
||||
wdb.getRenewalBlock = () => {
|
||||
return network.genesis.hash;
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await wdb.open();
|
||||
wallet = await wdb.create();
|
||||
// rollout all names
|
||||
wdb.height = 52 * 144 * 7;
|
||||
network.names.noRollout = true;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
network.names.noRollout = false;
|
||||
await wdb.close();
|
||||
});
|
||||
|
||||
|
|
@ -3290,24 +3277,19 @@ describe('Wallet', function() {
|
|||
const fund = 10e6;
|
||||
// Store height of auction OPEN to be used in second bid.
|
||||
// The main test wallet, and wallet that will receive the FINALIZE.
|
||||
/** @type {Wallet} */
|
||||
let wallet;
|
||||
let unsentReveal;
|
||||
|
||||
// Hack required to focus test on txdb mechanics.
|
||||
// We don't otherwise need WalletDB or Blockchain
|
||||
// TODO: Remove this after #888 is merged.
|
||||
wdb.getRenewalBlock = () => {
|
||||
return network.genesis.hash;
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await wdb.open();
|
||||
wallet = await wdb.create();
|
||||
// rollout all names
|
||||
wdb.height = 52 * 144 * 7;
|
||||
network.names.noRollout = true;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
network.names.noRollout = false;
|
||||
await wdb.close();
|
||||
});
|
||||
|
||||
|
|
@ -3748,13 +3730,6 @@ describe('Wallet', function() {
|
|||
const network = Network.get('regtest');
|
||||
const wdb = new WalletDB({ network });
|
||||
|
||||
// Hack required to focus test on txdb mechanics.
|
||||
// We don't otherwise need WalletDB or Blockchain
|
||||
// TODO: Remove this after #888 is merged.
|
||||
wdb.getRenewalBlock = () => {
|
||||
return network.genesis.hash;
|
||||
};
|
||||
|
||||
const mineBlocks = async (count) => {
|
||||
for (let i = 0; i < count; i++) {
|
||||
await wdb.addBlock(nextEntry(wdb), []);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ const WalletDB = require('../lib/wallet/walletdb');
|
|||
const Wallet = require('../lib/wallet/wallet');
|
||||
const Account = require('../lib/wallet/account');
|
||||
const wutils = require('./util/wallet');
|
||||
const {nextEntry, fakeEntry} = require('./util/wallet');
|
||||
const {nextEntry, fakeEntry} = wutils;
|
||||
const {dummyInput} = require('./util/primitives');
|
||||
const MemWallet = require('./util/memwallet');
|
||||
|
||||
/** @typedef {import('../lib/primitives/tx')} TX */
|
||||
|
|
@ -541,7 +542,7 @@ describe('Wallet Unit Tests', () => {
|
|||
|
||||
function fakeTX(addr) {
|
||||
const tx = new MTX();
|
||||
tx.addInput(wutils.dummyInput());
|
||||
tx.addInput(dummyInput());
|
||||
tx.addOutput({
|
||||
address: addr,
|
||||
value: 5460
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue