Skip to content

Commit

Permalink
wdb: fix change address migration for hsd v2.1.3 and before nodes.
Browse files Browse the repository at this point in the history
  • Loading branch information
nodech committed Jul 5, 2024
1 parent 8402cd0 commit bc98d66
Show file tree
Hide file tree
Showing 8 changed files with 1,215 additions and 356 deletions.
165 changes: 151 additions & 14 deletions lib/wallet/migrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@ const assert = require('bsert');
const Logger = require('blgr');
const bdb = require('bdb');
const bio = require('bufio');
const {HDPublicKey} = require('../hd/hd');
const binary = require('../utils/binary');
const {encoding} = bio;
const Network = require('../protocol/network');
const Account = require('./account');
const WalletKey = require('./walletkey');
const Path = require('./path');
const Script = require('../script/script');
const MapRecord = require('./records').MapRecord;
const AbstractMigration = require('../migrations/migration');
const {
MigrationResult,
Expand Down Expand Up @@ -119,6 +126,7 @@ class MigrateChangeAddress extends AbstractMigration {
this.logger = options.logger.context('change-address-migration');
this.db = options.db;
this.ldb = options.ldb;
this.layout = MigrateChangeAddress.layout();
}

/**
Expand All @@ -138,33 +146,76 @@ class MigrateChangeAddress extends AbstractMigration {
*/

async migrate(b, pending) {
const wids = await this.db.getWallets();
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]
});

let total = 0;
for (const wid of wids) {
const wallet = await this.db.get(wid);

this.logger.info('Checking wallet (id=%s, wid=%d).',
wallet.id, wid);

total += await this.migrateWallet(b, wallet);
this.logger.info('Checking wallet (wid=%d).', wid);
total += await this.migrateWallet(b, wid);
}

if (total > 0)
pending.rescan = true;
}

async migrateWallet(b, wallet) {
async migrateWallet(b, wid) {
const accounts = this.ldb.iterator({
gte: this.layout.wdb.a.min(wid),
lte: this.layout.wdb.a.max(wid),
values: true
});

let total = 0;
for (let i = 0; i < wallet.accountDepth; i++) {
const account = await wallet.getAccount(i);
for await (const {key, value} of accounts) {
const [awid, aindex] = this.layout.wdb.a.decode(key);
const name = await this.ldb.get(this.layout.wdb.n.encode(wid, aindex));
assert(awid === wid);
const br = bio.read(value);
const initialized = br.readU8();

if (!initialized)
continue;

const type = br.readU8();
const m = br.readU8();
const n = br.readU8();
br.seek(4); // skip receive
const changeDepth = br.readU32();
const lookahead = br.readU8();
const accountKey = this.readKey(br);
const count = br.readU8();
assert(br.left() === count * 74);

const keys = [];

for (let i = 0; i < count; i++) {
const key = this.readKey(br);
const cmp = (a, b) => a.compare(b);
binary.insert(keys, key, cmp, true);
}

for (let i = 0; i < changeDepth + lookahead; i++) {
const key = this.deriveKey({
accountName: name,
accountIndex: aindex,
accountKey: accountKey,
type: type,
m: m,
n: n,
branch: 1,
index: i,
keys: keys
});

for (let j = 0; j < account.changeDepth + account.lookahead; j++) {
const key = account.deriveChange(j);
const path = key.toPath();

if (!await this.db.hasPath(account.wid, path.hash)) {
await this.db.savePath(b, account.wid, path);
if (!await this.hasPath(wid, path.hash)) {
await this.savePath(b, wid, path);
total += 1;
}
}
Expand All @@ -173,6 +224,63 @@ class MigrateChangeAddress extends AbstractMigration {
return total;
}

deriveKey(options) {
const key = options.accountKey.derive(options.branch).derive(options.index);
const wkey = new WalletKey();
wkey.keyType = Path.types.HD;
wkey.name = options.accountName;
wkey.account = options.accountIndex;
wkey.branch = options.branch;
wkey.index = options.index;
wkey.publicKey = key.publicKey;

const keys = [];
switch (options.type) {
case Account.types.PUBKEYHASH:
break;

case Account.types.MULTISIG:
keys.push(wkey.publicKey);

for (const shared of options.keys) {
const key = shared.derive(options.branch).derive(options.index);
keys.push(key.publicKey);
}

wkey.script = Script.fromMultisig(options.m, options.n, keys);

break;
}

return wkey;
}

readKey(br) {
const key = new HDPublicKey();
key.depth = br.readU8();
key.parentFingerPrint = br.readU32BE();
key.childIndex = br.readU32BE();
key.chainCode = br.readBytes(32);
key.publicKey = br.readBytes(33);
return key;
}

async hasPath(wid, hash) {
return this.ldb.has(this.layout.wdb.P.encode(wid, hash));
}

async savePath(b, wid, path) {
const wlayout = this.layout.wdb;

const data = await this.ldb.get(wlayout.p.encode(path.hash));
const map = data ? MapRecord.decode(data) : new MapRecord();

map.add(wid);
b.put(wlayout.p.encode(path.hash), map.encode());
b.put(wlayout.P.encode(wid, path.hash), path.encode());
b.put(wlayout.r.encode(wid, path.account, path.hash), null);
}

/**
* Return info about the migration.
* @returns {String}
Expand All @@ -184,6 +292,35 @@ class MigrateChangeAddress extends AbstractMigration {
description: 'Wallet is corrupted.'
};
}

/**
* Get layout that migration is going to affect.
* @returns {Object}
*/

static layout() {
return {
wdb: {
// W[wid] -> wallet id
W: bdb.key('W', ['uint32']),

// n[wid][index] -> account name
n: bdb.key('n', ['uint32', 'uint32']),

// a[wid][index] -> account
a: bdb.key('a', ['uint32', 'uint32']),

// p[addr-hash] -> address->wid map
p: bdb.key('p', ['hash']),

// P[wid][addr-hash] -> path data
P: bdb.key('P', ['uint32', 'hash']),

// r[wid][index][addr-hash] -> dummy (addr by account)
r: bdb.key('r', ['uint32', 'uint32', 'hash'])
}
};
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use strict';

// walletdb version 0 to 1 migration.
/**
* walletdb version 0 to 1 migration.
* Final migration data needs modifications for
* the correct next migration value.
*/

const Network = require('../../../lib/protocol/network');
const WalletDB = require('../../../lib/wallet/walletdb');
const cutil = require('../../util/common');
const wutils = require('../../util/wallet');
const mutils = require('../../util/migrations');

const NETWORK = Network.get('regtest');

Expand All @@ -31,5 +34,5 @@ async function getMigrationDump(wdb) {
'M'
];

return wutils.dumpWDB(wdb, prefixes.map(cutil.prefix2hex));
return mutils.dumpWDB(wdb, prefixes.map(mutils.prefix2hex));
}
Loading

0 comments on commit bc98d66

Please sign in to comment.