1250 lines
32 KiB
JavaScript
1250 lines
32 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const fs = require('bfile');
|
|
const random = require('bcrypto/lib/random');
|
|
const Network = require('../lib/protocol/network');
|
|
const rules = require('../lib/covenants/rules');
|
|
const Coin = require('../lib/primitives/coin');
|
|
const WalletDB = require('../lib/wallet/walletdb');
|
|
const layouts = require('../lib/wallet/layout');
|
|
const TXDB = require('../lib/wallet/txdb');
|
|
const {Credit} = TXDB;
|
|
const WalletMigrator = require('../lib/wallet/migrations');
|
|
const {MigrateMigrations} = require('../lib/wallet/migrations');
|
|
const MigrationState = require('../lib/migrations/state');
|
|
const AbstractMigration = require('../lib/migrations/migration');
|
|
const {
|
|
types,
|
|
oldLayout
|
|
} = require('../lib/migrations/migrator');
|
|
const migutils = require('./util/migrations');
|
|
const {
|
|
migrationError,
|
|
writeVersion,
|
|
getVersion,
|
|
checkVersion,
|
|
checkEntries,
|
|
checkExactEntries,
|
|
fillEntries
|
|
} = migutils;
|
|
const {rimraf, testdir} = require('./util/common');
|
|
|
|
const NETWORK = 'regtest';
|
|
const network = Network.get(NETWORK);
|
|
const layout = layouts.wdb;
|
|
|
|
const countAndTimeData = [
|
|
require('./data/migrations/wallet-5-pagination.json'),
|
|
require('./data/migrations/wallet-5-pagination-2.json')
|
|
];
|
|
|
|
const wdbFlagError = (id) => {
|
|
return 'Restart with'
|
|
+ ` \`hsd --wallet-migrate=${id}\` or \`hsw --migrate=${id}\`\n`
|
|
+ '(Full node may be required for rescan)';
|
|
};
|
|
|
|
class MockMigration extends AbstractMigration {
|
|
async check() {
|
|
return types.MIGRATE;
|
|
}
|
|
|
|
async migrate(_, pending) {
|
|
return pending;
|
|
}
|
|
}
|
|
|
|
const mockMigrations = {
|
|
0: MigrateMigrations,
|
|
1: class Migration1 extends MockMigration {
|
|
static info() {
|
|
return {
|
|
name: 'mock migration 1',
|
|
description: 'desc mock migration 1'
|
|
};
|
|
}
|
|
},
|
|
2: class Migration2 extends MockMigration {
|
|
static info() {
|
|
return {
|
|
name: 'mock migration 2',
|
|
description: 'desc mock migration 2'
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
describe('Wallet Migrations', function() {
|
|
describe('General', function() {
|
|
const location = testdir('migrate-wallet-ensure');
|
|
const migrationsBAK = WalletMigrator.migrations;
|
|
WalletMigrator.migrations = mockMigrations;
|
|
const lastMigrationID = Math.max(...Object.keys(mockMigrations));
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network: network
|
|
};
|
|
|
|
const getIDs = (min, max) => {
|
|
const ids = new Set();
|
|
for (let i = min; i <= max; i++)
|
|
ids.add(i);
|
|
|
|
return ids;
|
|
};
|
|
|
|
let walletDB, ldb;
|
|
beforeEach(async () => {
|
|
await fs.mkdirp(location);
|
|
|
|
walletDB = new WalletDB(walletOptions);
|
|
ldb = walletDB.db;
|
|
|
|
WalletMigrator.migrations = mockMigrations;
|
|
|
|
await walletDB.open();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rimraf(location);
|
|
});
|
|
|
|
after(() => {
|
|
WalletMigrator.migrations = migrationsBAK;
|
|
});
|
|
|
|
it('should initialize fresh walletdb migration state', async () => {
|
|
const rawState = await ldb.get(layout.M.encode());
|
|
const state = MigrationState.decode(rawState);
|
|
|
|
assert.strictEqual(state.lastMigration, lastMigrationID);
|
|
assert.strictEqual(state.nextMigration, lastMigrationID + 1);
|
|
assert.strictEqual(state.skipped.length, 0);
|
|
assert.strictEqual(state.inProgress, false);
|
|
await walletDB.close();
|
|
});
|
|
|
|
it('should not migrate pre-old migration state w/o flag', async () => {
|
|
const b = ldb.batch();
|
|
b.del(layout.M.encode());
|
|
await b.write();
|
|
await walletDB.close();
|
|
|
|
const ids = getIDs(0, lastMigrationID);
|
|
const expectedError = migrationError(WalletMigrator.migrations, [...ids],
|
|
wdbFlagError(lastMigrationID));
|
|
|
|
await assert.rejects(async () => {
|
|
await walletDB.open();
|
|
}, {
|
|
message: expectedError
|
|
});
|
|
|
|
const rawState = await ldb.get(layout.M.encode());
|
|
const state = MigrationState.decode(rawState);
|
|
|
|
assert.strictEqual(state.nextMigration, 0);
|
|
assert.strictEqual(state.lastMigration, -1);
|
|
assert.strictEqual(state.skipped.length, 0);
|
|
assert.strictEqual(state.inProgress, false);
|
|
await ldb.close();
|
|
});
|
|
|
|
// special case
|
|
it('should not migrate from last old migration state w/o flag', async () => {
|
|
const b = ldb.batch();
|
|
b.del(layout.M.encode());
|
|
b.put(oldLayout.M.encode(0), null);
|
|
await b.write();
|
|
await walletDB.close();
|
|
|
|
const ids = getIDs(0, lastMigrationID);
|
|
ids.delete(1);
|
|
const expectedError = migrationError(WalletMigrator.migrations, [...ids],
|
|
wdbFlagError(lastMigrationID));
|
|
|
|
await assert.rejects(async () => {
|
|
await walletDB.open();
|
|
}, {
|
|
message: expectedError
|
|
});
|
|
|
|
const rawState = await ldb.get(layout.M.encode());
|
|
const state = MigrationState.decode(rawState);
|
|
|
|
assert.strictEqual(state.nextMigration, 0);
|
|
assert.strictEqual(state.lastMigration, -1);
|
|
assert.strictEqual(state.skipped.length, 0);
|
|
assert.strictEqual(state.inProgress, false);
|
|
|
|
await ldb.close();
|
|
});
|
|
|
|
it('should upgrade and run new migration with flag', async () => {
|
|
const b = ldb.batch();
|
|
b.del(layouts.wdb.M.encode());
|
|
b.put(oldLayout.M.encode(0), null);
|
|
writeVersion(b, layouts.wdb.V.encode(), 'wallet', 0);
|
|
await b.write();
|
|
await walletDB.close();
|
|
|
|
walletDB.options.walletMigrate = lastMigrationID;
|
|
walletDB.version = 1;
|
|
await walletDB.open();
|
|
|
|
const versionData = await ldb.get(layouts.wdb.V.encode());
|
|
const version = await getVersion(versionData, 'wallet');
|
|
assert.strictEqual(version, walletDB.version);
|
|
|
|
const rawState = await ldb.get(layout.M.encode());
|
|
const state = MigrationState.decode(rawState);
|
|
|
|
assert.strictEqual(state.lastMigration, lastMigrationID);
|
|
assert.strictEqual(state.nextMigration, lastMigrationID + 1);
|
|
assert.strictEqual(state.skipped.length, 0);
|
|
assert.strictEqual(state.inProgress, false);
|
|
await walletDB.close();
|
|
});
|
|
|
|
it('should only list last migration', async () => {
|
|
const LastMigration = class extends AbstractMigration {
|
|
async check() {
|
|
return types.MIGRATE;
|
|
}
|
|
|
|
static info() {
|
|
return {
|
|
name: 'last migration',
|
|
description: 'last migration'
|
|
};
|
|
}
|
|
};
|
|
|
|
const nextID = lastMigrationID + 1;
|
|
|
|
await walletDB.close();
|
|
WalletMigrator.migrations[nextID] = LastMigration;
|
|
|
|
let error;
|
|
try {
|
|
await walletDB.open();
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
|
|
assert(error, 'Chain must throw an error.');
|
|
const expected = migrationError(WalletMigrator.migrations, [nextID],
|
|
wdbFlagError(nextID));
|
|
assert.strictEqual(error.message, expected);
|
|
});
|
|
});
|
|
|
|
describe('Migrations #0 & #1 (data)', function() {
|
|
const location = testdir('migrate-wallet-0-1-int');
|
|
const migrationBAK = WalletMigrator.migrations;
|
|
const data = require('./data/migrations/wallet-0-migrate-migrations.json');
|
|
const Migration = WalletMigrator.MigrateMigrations;
|
|
const layout = Migration.layout();
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network
|
|
};
|
|
|
|
let wdb, ldb;
|
|
beforeEach(async () => {
|
|
WalletMigrator.migrations = {};
|
|
await fs.mkdirp(location);
|
|
|
|
wdb = new WalletDB(walletOptions);
|
|
ldb = wdb.db;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
WalletMigrator.migrations = migrationBAK;
|
|
await rimraf(location);
|
|
});
|
|
|
|
for (let i = 0; i < data.cases.length; i++) {
|
|
it(`should migrate ${data.cases[i].description}`, async () => {
|
|
const before = data.cases[i].before;
|
|
const after = data.cases[i].after;
|
|
await ldb.open();
|
|
const b = ldb.batch();
|
|
|
|
for (const [key, value] of Object.entries(before)) {
|
|
const bkey = Buffer.from(key, 'hex');
|
|
const bvalue = Buffer.from(value, 'hex');
|
|
|
|
b.put(bkey, bvalue);
|
|
}
|
|
|
|
writeVersion(b, layouts.wdb.V.encode(), 'wallet', 0);
|
|
|
|
await b.write();
|
|
await ldb.close();
|
|
|
|
WalletMigrator.migrations = {
|
|
0: Migration,
|
|
1: WalletMigrator.MigrateChangeAddress
|
|
};
|
|
|
|
wdb.options.walletMigrate = 1;
|
|
wdb.version = 1;
|
|
|
|
await wdb.open();
|
|
await checkVersion(ldb, layouts.wdb.V.encode(), 1);
|
|
await checkEntries(ldb, {
|
|
after,
|
|
throw: true,
|
|
bail: true
|
|
});
|
|
const oldM = await ldb.get(layout.oldLayout.wdb.M.encode(0));
|
|
assert.strictEqual(oldM, null);
|
|
await wdb.close();
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('Migrate change address (data)', function() {
|
|
const location = testdir('wallet-change-data');
|
|
const migrationsBAK = WalletMigrator.migrations;
|
|
const data = require('./data/migrations/wallet-1-change.json');
|
|
const Migration = WalletMigrator.MigrateChangeAddress;
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network
|
|
};
|
|
|
|
let wdb, ldb;
|
|
before(async () => {
|
|
WalletMigrator.migrations = {};
|
|
await fs.mkdirp(location);
|
|
|
|
wdb = new WalletDB(walletOptions);
|
|
ldb = wdb.db;
|
|
|
|
await ldb.open();
|
|
await fillEntries(ldb, data.beforeOnly);
|
|
await fillEntries(ldb, data.before);
|
|
await ldb.close();
|
|
});
|
|
|
|
after(async () => {
|
|
WalletMigrator.migrations = migrationsBAK;
|
|
await rimraf(location);
|
|
});
|
|
|
|
it('should have before entries', async () => {
|
|
wdb.version = 0;
|
|
try {
|
|
// We don't care that new wallet can't decode old data.
|
|
// It will still run migrations.
|
|
await wdb.open();
|
|
} catch (e) {
|
|
;
|
|
}
|
|
await checkVersion(ldb, layouts.wdb.V.encode(), 0);
|
|
await checkEntries(ldb, {
|
|
after: data.before,
|
|
throw: true,
|
|
bail: true
|
|
});
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should enable wallet migration', () => {
|
|
WalletMigrator.migrations = {
|
|
0: Migration
|
|
};
|
|
});
|
|
|
|
it('should fail without migrate flag', async () => {
|
|
const expectedError = migrationError(WalletMigrator.migrations, [0],
|
|
wdbFlagError(0));
|
|
|
|
await assert.rejects(async () => {
|
|
await wdb.open();
|
|
}, {
|
|
message: expectedError
|
|
});
|
|
|
|
await ldb.close();
|
|
});
|
|
|
|
it('should migrate', async () => {
|
|
wdb.options.walletMigrate = 0;
|
|
|
|
try {
|
|
// We don't care that new wallet can't decode old data.
|
|
// It will still run migrations.
|
|
await wdb.open();
|
|
} catch (e) {
|
|
;
|
|
}
|
|
await checkVersion(ldb, layouts.wdb.V.encode(), 0);
|
|
await checkEntries(ldb, {
|
|
after: data.after,
|
|
throw: true,
|
|
bail: true
|
|
});
|
|
|
|
await wdb.close();
|
|
});
|
|
});
|
|
|
|
describe('Migrate account lookahead (data)', function() {
|
|
const location = testdir('wallet-lookahead-data');
|
|
const migrationsBAK = WalletMigrator.migrations;
|
|
const data = require('./data/migrations/wallet-2-account-lookahead.json');
|
|
const Migration = WalletMigrator.MigrateAccountLookahead;
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network
|
|
};
|
|
|
|
let wdb, ldb;
|
|
before(async () => {
|
|
WalletMigrator.migrations = {};
|
|
await fs.mkdirp(location);
|
|
|
|
wdb = new WalletDB(walletOptions);
|
|
ldb = wdb.db;
|
|
|
|
await ldb.open();
|
|
await fillEntries(ldb, data.before);
|
|
await ldb.close();
|
|
});
|
|
|
|
after(async () => {
|
|
WalletMigrator.migrations = migrationsBAK;
|
|
await rimraf(location);
|
|
});
|
|
|
|
it('should have before entries', async () => {
|
|
wdb.version = 1;
|
|
try {
|
|
// We don't care that new wallet can't decode old data.
|
|
// It will still run migrations.
|
|
await wdb.open();
|
|
} catch (e) {
|
|
;
|
|
}
|
|
await checkVersion(ldb, layouts.wdb.V.encode(), 1);
|
|
await checkEntries(ldb, {
|
|
after: data.before,
|
|
throw: true,
|
|
bail: true
|
|
});
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should enable wallet migration', () => {
|
|
WalletMigrator.migrations = {
|
|
0: Migration
|
|
};
|
|
});
|
|
|
|
it('should fail without migrate flag', async () => {
|
|
const expectedError = migrationError(WalletMigrator.migrations, [0],
|
|
wdbFlagError(0));
|
|
|
|
await assert.rejects(async () => {
|
|
await wdb.open();
|
|
}, {
|
|
message: expectedError
|
|
});
|
|
|
|
await ldb.close();
|
|
});
|
|
|
|
it('should migrate', async () => {
|
|
wdb.options.walletMigrate = 0;
|
|
|
|
try {
|
|
// We don't care that new wallet can't decode old data.
|
|
// It will still run migrations.
|
|
await wdb.open();
|
|
} catch (e) {
|
|
;
|
|
}
|
|
await checkVersion(ldb, layouts.wdb.V.encode(), 2);
|
|
await checkEntries(ldb, {
|
|
after: data.after,
|
|
throw: true,
|
|
bail: true
|
|
});
|
|
await wdb.close();
|
|
});
|
|
});
|
|
|
|
describe('Migrate account lookahead (integration)', function () {
|
|
const location = testdir('wallet-lookahead');
|
|
const migrationsBAK = WalletMigrator.migrations;
|
|
const TEST_LOOKAHEAD = 150;
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network: network
|
|
};
|
|
|
|
let walletDB, ldb;
|
|
before(async () => {
|
|
WalletMigrator.migrations = {};
|
|
await fs.mkdirp(location);
|
|
});
|
|
|
|
after(async () => {
|
|
WalletMigrator.migrations = migrationsBAK;
|
|
await rimraf(location);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
walletDB = new WalletDB(walletOptions);
|
|
ldb = walletDB.db;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (ldb.opened)
|
|
await ldb.close();
|
|
});
|
|
|
|
const newToOld = (raw) => {
|
|
// flags, type, m, n, receiveDepth, changeDepth
|
|
const preLen = 1 + 1 + 1 + 1 + 4 + 4;
|
|
const pre = raw.slice(0, preLen);
|
|
const lookahead = raw.slice(preLen, preLen + 4);
|
|
const post = raw.slice(preLen + 4);
|
|
|
|
return Buffer.concat([
|
|
pre,
|
|
Buffer.alloc(1, lookahead[0]),
|
|
post
|
|
]);
|
|
};
|
|
|
|
it('should write old account record', async () => {
|
|
const setupLookahead = async (wallet, n) => {
|
|
const accounts = [];
|
|
let b;
|
|
|
|
for (let i = 0; i < 3; i++)
|
|
accounts.push(await wallet.getAccount(i));
|
|
|
|
b = ldb.batch();
|
|
for (let i = 0; i < 3; i++)
|
|
await accounts[i].setLookahead(b, n + i);
|
|
await b.write();
|
|
|
|
b = ldb.batch();
|
|
for (let i = 0; i < 3; i++) {
|
|
const encoded = newToOld(accounts[i].encode(), n + i);
|
|
b.put(layout.a.encode(wallet.wid, i), encoded);
|
|
}
|
|
|
|
// previous version
|
|
walletDB.writeVersion(b, 1);
|
|
await b.write();
|
|
};
|
|
|
|
await walletDB.open();
|
|
|
|
const wallet = walletDB.primary;
|
|
await wallet.createAccount({});
|
|
await wallet.createAccount({});
|
|
|
|
await setupLookahead(wallet, TEST_LOOKAHEAD + 0);
|
|
|
|
const wallet2 = await walletDB.create({});
|
|
await wallet2.createAccount({});
|
|
await wallet2.createAccount({});
|
|
await setupLookahead(wallet2, TEST_LOOKAHEAD + 10);
|
|
|
|
await walletDB.close();
|
|
});
|
|
|
|
it('should enable wallet account lookahead migration', () => {
|
|
WalletMigrator.migrations = {
|
|
0: WalletMigrator.MigrateAccountLookahead
|
|
};
|
|
});
|
|
|
|
it('should fail without migrate flag', async () => {
|
|
const expectedError = migrationError(WalletMigrator.migrations, [0],
|
|
wdbFlagError(0));
|
|
|
|
await assert.rejects(async () => {
|
|
await walletDB.open();
|
|
}, {
|
|
message: expectedError
|
|
});
|
|
|
|
await ldb.close();
|
|
});
|
|
|
|
it('should migrate with migrate flag', async () => {
|
|
const checkLookahead = async (wallet, n) => {
|
|
for (let i = 0; i < 3; i++) {
|
|
const account = await wallet.getAccount(i);
|
|
assert.strictEqual(account.lookahead, n + i);
|
|
}
|
|
};
|
|
|
|
walletDB.options.walletMigrate = 0;
|
|
walletDB.version = 2;
|
|
|
|
await walletDB.open();
|
|
const wallet = walletDB.primary;
|
|
const wallet2 = await walletDB.get(1);
|
|
await checkLookahead(wallet, TEST_LOOKAHEAD + 0);
|
|
await checkLookahead(wallet2, TEST_LOOKAHEAD + 10);
|
|
await walletDB.close();
|
|
});
|
|
});
|
|
|
|
describe('Migrate txdb balances (integration)', function() {
|
|
const location = testdir('walet-txdb-refresh');
|
|
const migrationsBAK = WalletMigrator.migrations;
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network
|
|
};
|
|
|
|
const balanceEquals = (balance, expected) => {
|
|
assert.strictEqual(balance.tx, expected.tx);
|
|
assert.strictEqual(balance.coin, expected.coin);
|
|
assert.strictEqual(balance.unconfirmed, expected.unconfirmed);
|
|
assert.strictEqual(balance.confirmed, expected.confirmed);
|
|
assert.strictEqual(balance.ulocked, expected.ulocked);
|
|
assert.strictEqual(balance.clocked, expected.clocked);
|
|
};
|
|
|
|
let walletDB, ldb;
|
|
before(async () => {
|
|
WalletMigrator.migrations = {};
|
|
await fs.mkdirp(location);
|
|
});
|
|
|
|
after(async () => {
|
|
WalletMigrator.migrations = migrationsBAK;
|
|
await rimraf(location);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
walletDB = new WalletDB(walletOptions);
|
|
ldb = walletDB.db;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (ldb.opened)
|
|
await ldb.close();
|
|
});
|
|
|
|
it('should write some coins w/o updating balance', async () => {
|
|
// generate credits for the first 10 addresses stored on initialization.
|
|
await walletDB.open();
|
|
|
|
const wallet = walletDB.primary;
|
|
|
|
await wallet.createAccount({
|
|
name: 'alt'
|
|
});
|
|
|
|
const randomCoin = (options) => {
|
|
const coin = new Coin({
|
|
version: 1,
|
|
coinbase: false,
|
|
hash: random.randomBytes(32),
|
|
index: 0,
|
|
...options
|
|
});
|
|
|
|
if (options.covenantType != null)
|
|
coin.covenant.type = options.covenantType;
|
|
|
|
return coin;
|
|
};
|
|
|
|
const coins = [];
|
|
const spentCoins = [];
|
|
|
|
const addCoin = (addr, spent, confirmed, bid) => {
|
|
const list = spent ? spentCoins : coins;
|
|
|
|
const coin = randomCoin({
|
|
value: 1e6,
|
|
address: addr.getAddress(),
|
|
height: confirmed ? 1 : -1
|
|
});
|
|
|
|
if (bid)
|
|
coin.covenant.type = rules.types.BID;
|
|
|
|
list.push(coin);
|
|
};
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
const addr0 = await wallet.createReceive(0);
|
|
const addr1 = await wallet.createReceive(1);
|
|
|
|
// 5 NONE coins to default account, of each type:
|
|
// confirmed spent,
|
|
// unconfirmed spent,
|
|
// unconfirmed unspent,
|
|
// confirmed unspent
|
|
|
|
// confirmed += 1e6 * 5;
|
|
// unconfirmed += 1e6 * 5;
|
|
// coin += 5;
|
|
addCoin(addr0, false, true);
|
|
|
|
// confirmed += 1e6 * 5;
|
|
// unconfirmed += 0;
|
|
// coin += 0;
|
|
addCoin(addr0, true, true);
|
|
|
|
// confirmed += 0;
|
|
// unconfirmed += 0;
|
|
// coin += 0;
|
|
addCoin(addr0, true, false);
|
|
|
|
// confirmed += 0;
|
|
// unconfirmed += 1e6 * 5;
|
|
// coin += 5;
|
|
addCoin(addr0, false, false);
|
|
|
|
// 5 BID coins to alt account, of each type:
|
|
// confirmed spent,
|
|
// unconfirmed spent,
|
|
// unconfirmed unspent,
|
|
// confirmed unspent
|
|
|
|
// confirmed += 1e6 * 5;
|
|
// unconfirmed += 1e6 * 5;
|
|
// coin += 5;
|
|
// locked += 1e6 * 5;
|
|
// unlocked += 1e6 * 5;
|
|
addCoin(addr1, false, true, true);
|
|
|
|
// confirmed += 1e6 * 5;
|
|
// unconfirmed += 0;
|
|
// coin += 0;
|
|
// locked += 1e6 * 5;
|
|
// unlocked += 0;
|
|
addCoin(addr1, true, true, true);
|
|
|
|
// confirmed += 0;
|
|
// unconfirmed += 0;
|
|
// coin += 0;
|
|
// locked += 0;
|
|
// unlocked += 0;
|
|
addCoin(addr1, true, false, true);
|
|
|
|
// confirmed += 0;
|
|
// unconfirmed += 1e6 * 5;
|
|
// coin += 5;
|
|
// locked += 0;
|
|
// unlocked += 1e6 * 5;
|
|
addCoin(addr1, false, false, true);
|
|
}
|
|
|
|
const batch = wallet.txdb.bucket.batch();
|
|
for (const coin of coins) {
|
|
const path = await wallet.txdb.getPath(coin);
|
|
const credit = new Credit(coin);
|
|
await wallet.txdb.saveCredit(batch, credit, path);
|
|
}
|
|
|
|
for (const coin of spentCoins) {
|
|
const path = await wallet.txdb.getPath(coin);
|
|
const credit = new Credit(coin, true);
|
|
await wallet.txdb.saveCredit(batch, credit, path);
|
|
}
|
|
|
|
await batch.write();
|
|
|
|
await walletDB.close();
|
|
});
|
|
|
|
it('should have incorrect balance before migration', async () => {
|
|
await walletDB.open();
|
|
|
|
const wallet = walletDB.primary;
|
|
const balance = await wallet.getBalance(-1);
|
|
const defBalance = await wallet.getBalance(0);
|
|
const altBalance = await wallet.getBalance(1);
|
|
|
|
const empty = {
|
|
tx: 0,
|
|
coin: 0,
|
|
unconfirmed: 0,
|
|
confirmed: 0,
|
|
ulocked: 0,
|
|
clocked: 0
|
|
};
|
|
|
|
balanceEquals(balance, empty);
|
|
balanceEquals(defBalance, empty);
|
|
balanceEquals(altBalance, empty);
|
|
|
|
await walletDB.close();
|
|
});
|
|
|
|
it('should enable txdb migration', () => {
|
|
WalletMigrator.migrations = {
|
|
0: WalletMigrator.MigrateTXDBBalances
|
|
};
|
|
});
|
|
|
|
it('should migrate', async () => {
|
|
walletDB.options.walletMigrate = 0;
|
|
|
|
await walletDB.open();
|
|
|
|
const wallet = walletDB.primary;
|
|
const balance = await wallet.getBalance(-1);
|
|
const defBalance = await wallet.getBalance(0);
|
|
const altBalance = await wallet.getBalance(1);
|
|
|
|
const expectedDefault = {
|
|
tx: 0,
|
|
coin: 10,
|
|
|
|
confirmed: 10e6,
|
|
unconfirmed: 10e6,
|
|
|
|
ulocked: 0,
|
|
clocked: 0
|
|
};
|
|
|
|
const expectedAlt = {
|
|
tx: 0,
|
|
coin: 10,
|
|
|
|
confirmed: 10e6,
|
|
unconfirmed: 10e6,
|
|
|
|
ulocked: 10e6,
|
|
clocked: 10e6
|
|
};
|
|
|
|
const expecteBalance = {
|
|
tx: expectedDefault.tx + expectedAlt.tx,
|
|
coin: expectedDefault.coin + expectedAlt.coin,
|
|
|
|
confirmed: expectedDefault.confirmed + expectedAlt.confirmed,
|
|
unconfirmed: expectedDefault.unconfirmed + expectedAlt.unconfirmed,
|
|
|
|
ulocked: expectedDefault.ulocked + expectedAlt.ulocked,
|
|
clocked: expectedDefault.clocked + expectedAlt.clocked
|
|
};
|
|
|
|
balanceEquals(defBalance, expectedDefault);
|
|
balanceEquals(altBalance, expectedAlt);
|
|
balanceEquals(balance, expecteBalance);
|
|
|
|
await walletDB.close();
|
|
});
|
|
});
|
|
|
|
describe('Bid Reveal Migration (integration)', function() {
|
|
const location = testdir('wallet-bid-reveal');
|
|
const migrationsBAK = WalletMigrator.migrations;
|
|
const data = require('./data/migrations/wallet-4-bid-reveal.json');
|
|
const Migration = WalletMigrator.MigrateBidRevealEntries;
|
|
const layout = Migration.layout();
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network
|
|
};
|
|
|
|
let walletDB, ldb;
|
|
before(async () => {
|
|
WalletMigrator.migrations = {};
|
|
await fs.mkdirp(location);
|
|
|
|
walletDB = new WalletDB(walletOptions);
|
|
ldb = walletDB.db;
|
|
|
|
await ldb.open();
|
|
|
|
const b = ldb.batch();
|
|
for (const [key, value] of Object.entries(data.before)) {
|
|
const bkey = Buffer.from(key, 'hex');
|
|
const bvalue = Buffer.from(value, 'hex');
|
|
|
|
b.put(bkey, bvalue);
|
|
}
|
|
await b.write();
|
|
|
|
await ldb.close();
|
|
});
|
|
|
|
after(async () => {
|
|
WalletMigrator.migrations = migrationsBAK;
|
|
await rimraf(location);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
walletDB = new WalletDB(walletOptions);
|
|
ldb = walletDB.db;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (ldb.opened)
|
|
await ldb.close();
|
|
});
|
|
|
|
it('should have before entries', async () => {
|
|
walletDB.version = 2;
|
|
await walletDB.open();
|
|
await checkVersion(ldb, layout.wdb.V.encode(), 2);
|
|
await checkEntries(ldb, {
|
|
after: data.before,
|
|
throw: true,
|
|
bail: true
|
|
});
|
|
await walletDB.close();
|
|
});
|
|
|
|
it('should enable wallet migration', () => {
|
|
WalletMigrator.migrations = {
|
|
0: Migration
|
|
};
|
|
});
|
|
|
|
it('should fail without migrate flag', async () => {
|
|
const expectedError = migrationError(WalletMigrator.migrations, [0],
|
|
wdbFlagError(0));
|
|
|
|
await assert.rejects(async () => {
|
|
await walletDB.open();
|
|
}, {
|
|
message: expectedError
|
|
});
|
|
|
|
await ldb.close();
|
|
});
|
|
|
|
it('should migrate', async () => {
|
|
walletDB.options.walletMigrate = 0;
|
|
|
|
walletDB.version = 3;
|
|
await walletDB.open();
|
|
// check we have migrated entries.
|
|
await checkEntries(ldb, {
|
|
after: data.after,
|
|
throw: true,
|
|
bail: true
|
|
});
|
|
await walletDB.close();
|
|
});
|
|
});
|
|
|
|
for (const [i, data] of countAndTimeData.entries())
|
|
describe(`TX Count and time indexing migration (integration ${i})`, function() {
|
|
const location = testdir('wallet-tx-count-time');
|
|
const migrationsBAK = WalletMigrator.migrations;
|
|
const Migration = WalletMigrator.MigrateTXCountTimeIndex;
|
|
const layout = Migration.layout();
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network
|
|
};
|
|
|
|
/** @type {WalletDB} */
|
|
let walletDB;
|
|
/** @type {bdb.DB} */
|
|
let ldb;
|
|
const headersByHash = new Map();
|
|
const headersByHeight = new Map();
|
|
|
|
before(async () => {
|
|
WalletMigrator.migrations = {};
|
|
await fs.mkdirp(location);
|
|
|
|
walletDB = new WalletDB(walletOptions);
|
|
ldb = walletDB.db;
|
|
|
|
await ldb.open();
|
|
|
|
const b = ldb.batch();
|
|
for (const [key, value] of Object.entries(data.before)) {
|
|
const bkey = Buffer.from(key, 'hex');
|
|
const bvalue = Buffer.from(value, 'hex');
|
|
|
|
b.put(bkey, bvalue);
|
|
}
|
|
|
|
await b.write();
|
|
await ldb.close();
|
|
|
|
// load headers into maps.
|
|
for (const header of data.headers) {
|
|
headersByHash.set(header.hash, header);
|
|
headersByHeight.set(header.height, header);
|
|
}
|
|
});
|
|
|
|
after(async () => {
|
|
WalletMigrator.migrations = migrationsBAK;
|
|
await rimraf(location);
|
|
});
|
|
|
|
it('should have before entries', async () => {
|
|
walletDB.version = 3;
|
|
await walletDB.open();
|
|
await checkVersion(ldb, layout.wdb.V.encode(), 3);
|
|
await checkEntries(ldb, {
|
|
after: data.before,
|
|
throw: true
|
|
});
|
|
await walletDB.close();
|
|
});
|
|
|
|
it('should enable wallet migration', () => {
|
|
WalletMigrator.migrations = {
|
|
0: WalletMigrator.MigrateTXCountTimeIndex
|
|
};
|
|
});
|
|
|
|
it('should fail without migrate flag', async () => {
|
|
const expectedError = migrationError(WalletMigrator.migrations, [0],
|
|
wdbFlagError(0));
|
|
|
|
await assert.rejects(async () => {
|
|
await walletDB.open();
|
|
}, {
|
|
message: expectedError
|
|
});
|
|
|
|
await ldb.close();
|
|
});
|
|
|
|
it('should migrate', async () => {
|
|
walletDB.options.walletMigrate = 0;
|
|
|
|
// patch getBlockHeader to return headers from data.
|
|
walletDB.client.getBlockHeader = (block) => {
|
|
if (typeof block === 'number')
|
|
return headersByHeight.get(block);
|
|
|
|
return headersByHash.get(block);
|
|
};
|
|
|
|
walletDB.version = 4;
|
|
await walletDB.open();
|
|
// check we have migrated entries.
|
|
await checkEntries(ldb, {
|
|
before: data.before,
|
|
after: data.after,
|
|
throw: true,
|
|
logErrors: true
|
|
});
|
|
|
|
await walletDB.close();
|
|
});
|
|
});
|
|
|
|
describe('Migrate Migration state v1 (data)', function() {
|
|
const location = testdir('wallet-migrate-migration-state-v1');
|
|
const data = require('./data/migrations/wallet-6-migrationstate-v1.json');
|
|
const migrationsBAK = WalletMigrator.migrations;
|
|
const Migration = WalletMigrator.MigrateMigrationStateV1;
|
|
|
|
const walletOptions = {
|
|
prefix: location,
|
|
memory: false,
|
|
network
|
|
};
|
|
|
|
let walletDB, ldb;
|
|
before(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();
|
|
});
|
|
|
|
after(async () => {
|
|
WalletMigrator.migrations = migrationsBAK;
|
|
await rimraf(location);
|
|
});
|
|
|
|
it('should migrate', async () => {
|
|
WalletMigrator.migrations = {
|
|
0: Migration
|
|
};
|
|
|
|
walletDB.options.walletMigrate = 0;
|
|
|
|
await walletDB.open();
|
|
await checkEntries(ldb, {
|
|
before: data.before,
|
|
after: data.after,
|
|
throw: true
|
|
});
|
|
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();
|
|
});
|
|
});
|
|
});
|