wallet: better/less property tracking.

This commit is contained in:
Christopher Jeffrey 2017-10-20 04:25:36 -07:00
parent fe261db706
commit 40bb08aed6
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 83 additions and 107 deletions

View File

@ -44,10 +44,10 @@ function HTTPServer(options) {
this.options = options;
this.network = this.options.network;
this.logger = this.options.logger.context('http');
this.walletdb = this.options.walletdb;
this.wdb = this.options.walletdb;
this.server = new HTTPBase(this.options);
this.rpc = this.walletdb.rpc;
this.rpc = this.wdb.rpc;
this.init();
}
@ -124,7 +124,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const token = valid.buf('token');
if (!this.options.walletAuth) {
const wallet = await this.walletdb.get(id);
const wallet = await this.wdb.get(id);
if (!wallet) {
res.send(404);
@ -138,7 +138,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
let wallet;
try {
wallet = await this.walletdb.auth(id, token);
wallet = await this.wdb.auth(id, token);
} catch (err) {
this.logger.info('Auth failure for %s: %s.', id, err.message);
res.error(403, err);
@ -162,12 +162,12 @@ HTTPServer.prototype.initRouter = function initRouter() {
res.send(200, { success: true });
await this.walletdb.rescan(height);
await this.wdb.rescan(height);
});
// Resend
this.post('/_admin/resend', async (req, res) => {
await this.walletdb.resend();
await this.wdb.resend();
res.send(200, { success: true });
});
@ -179,14 +179,14 @@ HTTPServer.prototype.initRouter = function initRouter() {
enforce(path, 'Path is required.');
await this.walletdb.backup(path);
await this.wdb.backup(path);
res.send(200, { success: true });
});
// List wallets
this.get('/_admin/wallets', async (req, res) => {
const wallets = await this.walletdb.getWallets();
const wallets = await this.wdb.getWallets();
res.send(200, wallets);
});
@ -205,7 +205,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
this.post('/', async (req, res) => {
const valid = req.valid();
const wallet = await this.walletdb.create({
const wallet = await this.wdb.create({
id: valid.str('id'),
type: valid.str('type'),
m: valid.u32('m'),
@ -227,7 +227,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
this.put('/:id', async (req, res) => {
const valid = req.valid();
const wallet = await this.walletdb.create({
const wallet = await this.wdb.create({
id: valid.str('id'),
type: valid.str('type'),
m: valid.u32('m'),
@ -425,7 +425,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const details = await req.wallet.getDetails(tx.hash('hex'));
res.send(200, details.toJSON(this.network));
res.send(200, details.toJSON(this.network, this.wdb.height));
});
// Create TX
@ -724,7 +724,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const result = [];
for (const item of details)
result.push(item.toJSON(this.network));
result.push(item.toJSON(this.network, this.wdb.height));
res.send(200, result);
});
@ -741,7 +741,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const result = [];
for (const item of details)
result.push(item.toJSON(this.network));
result.push(item.toJSON(this.network, this.wdb.height));
res.send(200, result);
});
@ -763,7 +763,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const result = [];
for (const item of details)
result.push(item.toJSON(this.network));
result.push(item.toJSON(this.network, this.wdb.height));
res.send(200, result);
});
@ -778,7 +778,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const result = [];
for (const item of details)
result.push(item.toJSON(this.network));
result.push(item.toJSON(this.network, this.wdb.height));
res.send(200, result);
});
@ -799,7 +799,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const details = await req.wallet.toDetails(tx);
res.send(200, details.toJSON(this.network));
res.send(200, details.toJSON(this.network, this.wdb.height));
});
// Resend
@ -822,32 +822,32 @@ HTTPServer.prototype.initSockets = function initSockets() {
this.handleSocket(socket);
});
this.walletdb.on('tx', (w, tx, details) => {
const json = details.toJSON(this.network);
this.wdb.on('tx', (w, tx, details) => {
const json = details.toJSON(this.network, this.wdb.height);
this.to(`w:${w.id}`, 'wallet tx', json);
});
this.walletdb.on('confirmed', (w, tx, details) => {
const json = details.toJSON(this.network);
this.wdb.on('confirmed', (w, tx, details) => {
const json = details.toJSON(this.network, this.wdb.height);
this.to(`w:${w.id}`, 'wallet confirmed', json);
});
this.walletdb.on('unconfirmed', (w, tx, details) => {
const json = details.toJSON(this.network);
this.wdb.on('unconfirmed', (w, tx, details) => {
const json = details.toJSON(this.network, this.wdb.height);
this.to(`w:${w.id}`, 'wallet unconfirmed', json);
});
this.walletdb.on('conflict', (w, tx, details) => {
const json = details.toJSON(this.network);
this.wdb.on('conflict', (w, tx, details) => {
const json = details.toJSON(this.network, this.wdb.height);
this.to(`w:${w.id}`, 'wallet conflict', json);
});
this.walletdb.on('balance', (w, balance) => {
this.wdb.on('balance', (w, balance) => {
const json = balance.toJSON();
this.to(`w:${w.id}`, 'wallet balance', json);
});
this.walletdb.on('address', (w, receive) => {
this.wdb.on('address', (w, receive) => {
const json = [];
for (const addr of receive)
@ -917,7 +917,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) {
let wallet;
try {
wallet = await this.walletdb.auth(id, token);
wallet = await this.wdb.auth(id, token);
} catch (e) {
this.logger.info('Wallet auth failure for %s: %s.', id, e.message);
throw new Error('Bad token.');

View File

@ -30,8 +30,6 @@ function Path(options) {
this.keyType = Path.types.HD;
this.id = null; // Passed in by caller.
this.wid = -1; // Passed in by caller.
this.name = null; // Passed in by caller.
this.account = 0;
this.branch = -1;
@ -71,8 +69,6 @@ Path.types = {
Path.prototype.fromOptions = function fromOptions(options) {
this.keyType = options.keyType;
this.id = options.id;
this.wid = options.wid;
this.name = options.name;
this.account = options.account;
this.branch = options.branch;
@ -108,8 +104,6 @@ Path.prototype.clone = function clone() {
path.keyType = this.keyType;
path.id = this.id;
path.wid = this.wid;
path.name = this.name;
path.account = this.account;
path.branch = this.branch;
@ -247,8 +241,6 @@ Path.prototype.toRaw = function toRaw() {
Path.prototype.fromAddress = function fromAddress(account, address) {
this.keyType = Path.types.ADDRESS;
this.id = account.id;
this.wid = account.wid;
this.name = account.name;
this.account = account.accountIndex;
this.version = address.version;
@ -309,7 +301,7 @@ Path.prototype.toJSON = function toJSON() {
*/
Path.prototype.inspect = function inspect() {
return `<Path: ${this.id}(${this.wid})/${this.name}:${this.toPath()}>`;
return `<Path: ${this.name}:${this.toPath()}>`;
};
/**

View File

@ -25,22 +25,19 @@ const TXRecord = records.TXRecord;
* TXDB
* @alias module:wallet.TXDB
* @constructor
* @param {Wallet} wallet
* @param {WalletDB} wdb
*/
function TXDB(wdb) {
function TXDB(wdb, wid) {
if (!(this instanceof TXDB))
return new TXDB(wdb);
this.wdb = wdb;
this.db = wdb.db;
this.logger = wdb.logger;
this.network = wdb.network;
this.options = wdb.options;
this.wid = 0;
this.id = null;
this.prefix = layout.prefix(0);
this.wid = wid || 0;
this.prefix = layout.prefix(this.wid);
this.wallet = null;
this.locked = new Set();
}
@ -58,10 +55,8 @@ TXDB.layout = layout;
*/
TXDB.prototype.open = async function open(wallet) {
const {wid, id} = wallet;
this.id = id;
this.wid = wid;
this.prefix = layout.prefix(wid);
this.wid = wallet.wid;
this.prefix = layout.prefix(this.wid);
this.wallet = wallet;
};
@ -147,7 +142,7 @@ TXDB.prototype.getPath = function getPath(output) {
if (!hash)
return null;
return this.wdb.getPath(this.wid, this.id, hash);
return this.wdb.getPath(this.wid, hash);
};
/**
@ -392,18 +387,18 @@ TXDB.prototype.getBlock = async function getBlock(height) {
/**
* Append to the global block record.
* @param {Hash} hash
* @param {BlockMeta} meta
* @param {BlockMeta} block
* @returns {Promise}
*/
TXDB.prototype.addBlock = async function addBlock(b, hash, meta) {
const key = layout.b(meta.height);
TXDB.prototype.addBlock = async function addBlock(b, hash, block) {
const key = layout.b(block.height);
const data = await this.get(key);
if (!data) {
const block = BlockRecord.fromMeta(meta);
block.add(hash);
b.put(key, block.toRaw());
const blk = BlockRecord.fromMeta(block);
blk.add(hash);
b.put(key, blk.toRaw());
return;
}
@ -526,7 +521,7 @@ TXDB.prototype.insert = async function insert(wtx, block) {
const b = this.bucket();
const {tx, hash} = wtx;
const height = block ? block.height : -1;
const details = new Details(this, wtx, block);
const details = new Details(wtx, block);
const state = new BalanceDelta();
let own = false;
@ -682,7 +677,7 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
const b = this.bucket();
const {tx, hash} = wtx;
const height = block.height;
const details = new Details(this, wtx, block);
const details = new Details(wtx, block);
const state = new BalanceDelta();
wtx.setBlock(block);
@ -697,21 +692,18 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
const input = tx.inputs[i];
const {hash, index} = input.prevout;
let credit = credits[i];
let resolved = false;
// There may be new credits available
// that we haven't seen yet.
if (!credit) {
if (!credits[i]) {
await this.removeInput(b, tx, i);
credit = await this.getCredit(hash, index);
const credit = await this.getCredit(hash, index);
if (!credit)
continue;
const path = await this.getPath(credit.coin);
assert(path);
// Add a spend record and undo coin
// for the coin we now know is ours.
// We don't need to remove the coin
@ -719,10 +711,11 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
// first place.
this.spendCredit(b, credit, tx, i);
state.coin(path, -1);
state.unconfirmed(path, -credit.coin.value);
credits[i] = credit;
resolved = true;
}
const credit = credits[i];
const coin = credit.coin;
assert(coin.height !== -1);
@ -732,6 +725,11 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
details.setInput(i, path, coin);
if (resolved) {
state.coin(path, -1);
state.unconfirmed(path, -coin.value);
}
// We can now safely remove the credit
// entirely, now that we know it's also
// been removed on-chain.
@ -828,7 +826,7 @@ TXDB.prototype.erase = async function erase(wtx, block) {
const b = this.bucket();
const {tx, hash} = wtx;
const height = block ? block.height : -1;
const details = new Details(this, wtx, block);
const details = new Details(wtx, block);
const state = new BalanceDelta();
if (!tx.isCoinbase()) {
@ -1015,7 +1013,7 @@ TXDB.prototype.unconfirm = async function unconfirm(hash) {
TXDB.prototype.disconnect = async function disconnect(wtx, block) {
const b = this.bucket();
const {tx, hash, height} = wtx;
const details = new Details(this, wtx, block);
const details = new Details(wtx, block);
const state = new BalanceDelta();
assert(block);
@ -1780,9 +1778,9 @@ TXDB.prototype.getCoinView = async function getCoinView(tx) {
if (tx.isCoinbase())
return view;
for (const input of tx.inputs) {
const prevout = input.prevout;
const coin = await this.getCoin(prevout.hash, prevout.index);
for (const {prevout} of tx.inputs) {
const {hash, index} = prevout;
const coin = await this.getCoin(hash, index);
if (!coin)
continue;
@ -1881,7 +1879,7 @@ TXDB.prototype.toDetails = async function toDetails(wtxs) {
TXDB.prototype._toDetails = async function _toDetails(wtx) {
const tx = wtx.tx;
const block = wtx.getBlock();
const details = new Details(this, wtx, block);
const details = new Details(wtx, block);
const coins = await this.getSpentCoins(tx);
for (let i = 0; i < tx.inputs.length; i++) {
@ -2083,8 +2081,8 @@ TXDB.prototype.zap = async function zap(acct, age) {
assert(now - wtx.mtime >= age);
this.logger.debug('Zapping TX: %s (%s)',
wtx.tx.txid(), this.id);
this.logger.debug('Zapping TX: %s (%d)',
wtx.tx.txid(), this.wid);
await this.remove(wtx.hash);
@ -2365,13 +2363,9 @@ Credit.fromTX = function fromTX(tx, index, height) {
* @param {TX} tx
*/
function Details(txdb, wtx, block) {
function Details(wtx, block) {
if (!(this instanceof Details))
return new Details(txdb, wtx, block);
this.wid = txdb.wid;
this.id = txdb.id;
this.tip = txdb.wdb.state.height;
return new Details(wtx, block);
this.hash = wtx.hash;
this.tx = wtx.tx;
@ -2452,11 +2446,14 @@ Details.prototype.setOutput = function setOutput(i, path) {
* @returns {Number}
*/
Details.prototype.getDepth = function getDepth() {
Details.prototype.getDepth = function getDepth(height) {
if (this.height === -1)
return 0;
const depth = this.tip - this.height;
if (height == null)
return 0;
const depth = height - this.height;
if (depth < 0)
return 0;
@ -2503,13 +2500,11 @@ Details.prototype.getRate = function getRate(fee) {
* @returns {Object}
*/
Details.prototype.toJSON = function toJSON(network) {
Details.prototype.toJSON = function toJSON(network, height) {
const fee = this.getFee();
const rate = this.getRate(fee);
return {
wid: this.wid,
id: this.id,
hash: util.revHex(this.hash),
height: this.height,
block: this.block ? util.revHex(this.block) : null,
@ -2521,7 +2516,7 @@ Details.prototype.toJSON = function toJSON(network) {
virtualSize: this.vsize,
fee: fee,
rate: rate,
confirmations: this.getDepth(),
confirmations: this.getDepth(height),
inputs: this.inputs.map((input) => {
return input.getJSON(network);
}),

View File

@ -895,7 +895,7 @@ Wallet.prototype.hasAddress = async function hasAddress(address) {
Wallet.prototype.getPath = async function getPath(address) {
const hash = Address.getHash(address, 'hex');
return this.wdb.getPath(this.wid, this.id, hash);
return this.wdb.getPath(this.wid, hash);
};
/**
@ -907,7 +907,7 @@ Wallet.prototype.getPath = async function getPath(address) {
Wallet.prototype.readPath = async function readPath(address) {
const hash = Address.getHash(address, 'hex');
return this.wdb.readPath(this.wid, this.id, hash);
return this.wdb.readPath(this.wid, hash);
};
/**
@ -935,7 +935,6 @@ Wallet.prototype.getPaths = async function getPaths(acct) {
const result = [];
for (const path of paths) {
path.id = this.id;
path.name = await this.getAccountName(path.account);
assert(path.name);

View File

@ -80,6 +80,7 @@ function WalletDB(options) {
}
this.state = new ChainState();
this.height = 0;
this.wallets = new Map();
this.depth = 0;
this.rescanning = false;
@ -303,6 +304,7 @@ WalletDB.prototype.init = async function init() {
if (cache) {
this.state = cache;
this.height = cache.height;
return;
}
@ -337,6 +339,7 @@ WalletDB.prototype.init = async function init() {
await b.write();
this.state = state;
this.height = state.height;
};
/**
@ -827,7 +830,6 @@ WalletDB.prototype._rename = async function _rename(wallet, id) {
b.del(layout.l(old));
wallet.id = id;
wallet.txdb.id = id;
this.save(b, wallet);
@ -1085,8 +1087,8 @@ WalletDB.prototype.savePath = async function savePath(b, wid, path) {
* @returns {Promise}
*/
WalletDB.prototype.getPath = async function getPath(wid, id, hash) {
const path = await this.readPath(wid, id, hash);
WalletDB.prototype.getPath = async function getPath(wid, hash) {
const path = await this.readPath(wid, hash);
if (!path)
return null;
@ -1104,15 +1106,13 @@ WalletDB.prototype.getPath = async function getPath(wid, id, hash) {
* @returns {Promise}
*/
WalletDB.prototype.readPath = async function readPath(wid, id, hash) {
WalletDB.prototype.readPath = async function readPath(wid, hash) {
const data = await this.db.get(layout.P(wid, hash));
if (!data)
return null;
const path = Path.fromRaw(data);
path.id = id;
path.wid = wid;
path.hash = hash;
return path;
@ -1206,7 +1206,6 @@ WalletDB.prototype.getWalletPaths = async function getWalletPaths(wid) {
const path = Path.fromRaw(item.value);
path.hash = hash;
path.wid = wid;
paths.push(path);
}
@ -1453,6 +1452,7 @@ WalletDB.prototype.syncState = async function syncState(tip) {
await b.write();
this.state = state;
this.height = state.height;
};
/**
@ -1472,6 +1472,7 @@ WalletDB.prototype.markState = async function markState(block) {
await b.write();
this.state = state;
this.height = state.height;
};
/**

View File

@ -26,8 +26,6 @@ function WalletKey(options, network) {
this.keyType = Path.types.HD;
this.id = null;
this.wid = -1;
this.name = null;
this.account = -1;
this.branch = -1;
@ -121,8 +119,6 @@ WalletKey.fromSecret = function fromSecret(data, network) {
WalletKey.prototype.toJSON = function toJSON() {
return {
network: this.network.type,
wid: this.wid,
id: this.id,
name: this.name,
account: this.account,
branch: this.branch,
@ -169,8 +165,6 @@ WalletKey.fromRaw = function fromRaw(data) {
WalletKey.prototype.fromHD = function fromHD(account, key, branch, index) {
this.keyType = Path.types.HD;
this.id = account.id;
this.wid = account.wid;
this.name = account.name;
this.account = account.accountIndex;
this.branch = branch;
@ -207,8 +201,6 @@ WalletKey.fromHD = function fromHD(account, key, branch, index) {
WalletKey.prototype.fromImport = function fromImport(account, data) {
this.keyType = Path.types.KEY;
this.id = account.id;
this.wid = account.wid;
this.name = account.name;
this.account = account.accountIndex;
this.witness = account.witness;
@ -236,8 +228,6 @@ WalletKey.fromImport = function fromImport(account, data) {
WalletKey.prototype.fromRing = function fromRing(account, ring) {
this.keyType = Path.types.KEY;
this.id = account.id;
this.wid = account.wid;
this.name = account.name;
this.account = account.accountIndex;
this.witness = account.witness;
@ -263,8 +253,6 @@ WalletKey.fromRing = function fromRing(account, ring) {
WalletKey.prototype.toPath = function toPath() {
const path = new Path();
path.id = this.id;
path.wid = this.wid;
path.name = this.name;
path.account = this.account;

View File

@ -1318,7 +1318,8 @@ describe('Wallet', function() {
const details = await wallet.toDetails(txs);
assert.strictEqual(details[0].toJSON().id, 'test');
assert(details.length > 0);
assert.strictEqual(wallet.id, 'test');
});
it('should change passphrase with encrypted imports', async () => {