txdb: refactor x2.

This commit is contained in:
Christopher Jeffrey 2016-10-18 18:17:39 -07:00
parent 3412916c89
commit 04f071f9b0
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 161 additions and 348 deletions

View File

@ -3,7 +3,6 @@
exports.Account = require('./account');
exports.MasterKey = require('./masterkey');
exports.Path = require('./path');
exports.PathInfo = require('./pathinfo');
exports.TXDB = require('./txdb');
exports.WalletDB = require('./walletdb');
exports.Wallet = require('./wallet');

View File

@ -1,313 +0,0 @@
/*!
* pathinfo.js - pathinfo object for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var utils = require('../utils/utils');
/**
* Path Info
* @constructor
* @param {WalletDB} db
* @param {WalletID} wid
* @param {TX} tx
* @param {Object} table
*/
function PathInfo(wallet, tx, paths) {
if (!(this instanceof PathInfo))
return new PathInfo(wallet, tx, paths);
// All relevant Accounts for
// inputs and outputs (for database indexing).
this.accounts = [];
// All output paths (for deriving during sync).
this.paths = [];
// Wallet
this.wallet = wallet;
// Wallet ID
this.wid = wallet.wid;
// Wallet Label
this.id = wallet.id;
// Map of address hashes->paths.
this.pathMap = {};
// Current transaction.
this.tx = null;
// Wallet-specific details cache.
this._details = null;
this._json = null;
if (tx)
this.fromTX(tx, paths);
}
/**
* Instantiate path info from a transaction.
* @private
* @param {TX} tx
* @param {Object} table
* @returns {PathInfo}
*/
PathInfo.prototype.fromTX = function fromTX(tx, paths) {
var uniq = {};
var i, hashes, hash, path;
this.tx = tx;
for (i = 0; i < paths.length; i++) {
path = paths[i];
this.pathMap[path.hash] = path;
if (!uniq[path.account]) {
uniq[path.account] = true;
this.accounts.push(path.account);
}
}
hashes = tx.getOutputHashes('hex');
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
path = this.pathMap[hash];
if (path)
this.paths.push(path);
}
return this;
};
/**
* Instantiate path info from a transaction.
* @param {WalletDB} db
* @param {WalletID} wid
* @param {TX} tx
* @param {Object} table
* @returns {PathInfo}
*/
PathInfo.fromTX = function fromTX(wallet, tx, paths) {
return new PathInfo(wallet).fromTX(tx, paths);
};
/**
* Test whether the map has paths
* for a given address hash.
* @param {Hash} hash
* @returns {Boolean}
*/
PathInfo.prototype.hasPath = function hasPath(output) {
var hash = output.getHash('hex');
if (!hash)
return false;
return this.pathMap[hash] != null;
};
/**
* Get path for a given address hash.
* @param {Hash} hash
* @returns {Path}
*/
PathInfo.prototype.getPath = function getPath(output) {
var hash = output.getHash('hex');
if (!hash)
return;
return this.pathMap[hash];
};
/**
* Convert path info to transaction details.
* @returns {Details}
*/
PathInfo.prototype.toDetails = function toDetails() {
var details = this._details;
if (!details) {
details = new Details(this);
this._details = details;
}
return details;
};
/**
* Convert path info to JSON details (caches json).
* @returns {Object}
*/
PathInfo.prototype.toJSON = function toJSON() {
var json = this._json;
if (!json) {
json = this.toDetails().toJSON();
this._json = json;
}
return json;
};
/**
* Transaction Details
* @constructor
* @param {PathInfo} info
*/
function Details(info) {
if (!(this instanceof Details))
return new Details(info);
this.db = info.wallet.db;
this.network = this.db.network;
this.wid = info.wid;
this.id = info.id;
this.hash = info.tx.hash('hex');
this.height = info.tx.height;
this.block = info.tx.block;
this.index = info.tx.index;
this.confirmations = info.tx.getConfirmations(this.db.height);
this.fee = info.tx.getFee();
this.ts = info.tx.ts;
this.ps = info.tx.ps;
this.tx = info.tx;
this.inputs = [];
this.outputs = [];
this.init(info.pathMap);
}
/**
* Initialize transactions details
* by pushing on mapped members.
* @private
* @param {Object} table
*/
Details.prototype.init = function init(map) {
this._insert(this.tx.inputs, true, this.inputs, map);
this._insert(this.tx.outputs, false, this.outputs, map);
};
/**
* Insert members in the input or output vector.
* @private
* @param {Input[]|Output[]} vector
* @param {Array} target
* @param {Object} table
*/
Details.prototype._insert = function _insert(vector, input, target, map) {
var i, io, address, hash, path, member;
for (i = 0; i < vector.length; i++) {
io = vector[i];
member = new DetailsMember();
if (input) {
if (io.coin)
member.value = io.coin.value;
} else {
member.value = io.value;
}
address = io.getAddress();
if (address) {
member.address = address;
hash = address.getHash('hex');
path = map[hash];
if (path)
member.path = path;
}
target.push(member);
}
};
/**
* Convert details to a more json-friendly object.
* @returns {Object}
*/
Details.prototype.toJSON = function toJSON() {
var self = this;
return {
wid: this.wid,
id: this.id,
hash: utils.revHex(this.hash),
height: this.height,
block: this.block ? utils.revHex(this.block) : null,
ts: this.ts,
ps: this.ps,
index: this.index,
fee: utils.btc(this.fee),
confirmations: this.confirmations,
inputs: this.inputs.map(function(input) {
return input.toJSON(self.network);
}),
outputs: this.outputs.map(function(output) {
return output.toJSON(self.network);
}),
tx: this.tx.toRaw().toString('hex')
};
};
/**
* Transaction Details Member
* @constructor
* @property {Number} value
* @property {Address} address
* @property {Path} path
*/
function DetailsMember() {
if (!(this instanceof DetailsMember))
return new DetailsMember();
this.value = 0;
this.address = null;
this.path = null;
}
/**
* Convert the member to a more json-friendly object.
* @param {Network} network
* @returns {Object}
*/
DetailsMember.prototype.toJSON = function toJSON(network) {
return {
value: utils.btc(this.value),
address: this.address
? this.address.toBase58(network)
: null,
path: this.path
? this.path.toJSON()
: null
};
};
/*
* Expose
*/
module.exports = PathInfo;

View File

@ -34,6 +34,7 @@ var DUMMY = new Buffer([0]);
* M[account][time][hash] -> dummy (tx by time + account)
* H[account][height][hash] -> dummy (tx by height + account)
* C[account][hash][index] -> dummy (coin by account)
* r[hash] -> dummy (replace by fee chain)
*/
var layout = {
@ -324,7 +325,7 @@ TXDB.prototype.commit = co(function* commit() {
* @private
* @param {String} event
* @param {Object} data
* @param {PathInfo} details
* @param {Details} details
*/
TXDB.prototype.emit = function emit(event, data, details) {
@ -424,9 +425,9 @@ TXDB.prototype.values = function values(options) {
};
/**
* Map a transactions' addresses to wallet IDs.
* @param {TX} tx
* @returns {Promise} - Returns {@link PathInfo}.
* Get wallet path for output.
* @param {Output} output
* @returns {Promise} - Returns {@link Path}.
*/
TXDB.prototype.getPath = function getPath(output) {
@ -436,9 +437,9 @@ TXDB.prototype.getPath = function getPath(output) {
};
/**
* Map a transactions' addresses to wallet IDs.
* @param {TX} tx
* @returns {Promise} - Returns {@link PathInfo}.
* Test whether path exists for output.
* @param {Output} output
* @returns {Promise} - Returns Boolean.
*/
TXDB.prototype.hasPath = function hasPath(output) {
@ -497,12 +498,9 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
input = tx.inputs[i];
prevout = input.prevout;
spent = yield this.getSpent(prevout.hash, prevout.index);
if (spent) {
coin = yield this.getSpentCoin(spent, prevout);
assert(coin);
coin = yield this.getCoin(prevout.hash, prevout.index);
if (coin) {
if (this.options.verify && tx.height === -1) {
input.coin = coin;
if (!(yield tx.verifyInputAsync(i)))
@ -512,9 +510,12 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
continue;
}
coin = yield this.getCoin(prevout.hash, prevout.index);
spent = yield this.getSpent(prevout.hash, prevout.index);
if (spent) {
coin = yield this.getSpentCoin(spent, prevout);
assert(coin);
if (coin) {
if (this.options.verify && tx.height === -1) {
input.coin = coin;
if (!(yield tx.verifyInputAsync(i)))
@ -2373,8 +2374,12 @@ TXDB.prototype.abandon = co(function* abandon(hash) {
return yield this.remove(hash);
});
/*
/**
* Balance
* @constructor
* @param {WalletID} wid
* @param {String} id
* @param {Number} account
*/
function Balance(wid, id, account) {
@ -2388,12 +2393,24 @@ function Balance(wid, id, account) {
this.confirmed = 0;
}
/**
* Test whether a balance is equal.
* @param {Balance} balance
* @returns {Boolean}
*/
Balance.prototype.equal = function equal(balance) {
return this.wid === balance.wid
&& this.confirmed === balance.confirmed
&& this.unconfirmed === balance.unconfirmed;
};
/**
* Convert balance to a more json-friendly object.
* @param {Boolean?} minimal
* @returns {Object}
*/
Balance.prototype.toJSON = function toJSON(minimal) {
return {
wid: !minimal ? this.wid : undefined,
@ -2404,6 +2421,11 @@ Balance.prototype.toJSON = function toJSON(minimal) {
};
};
/**
* Convert balance to human-readable string.
* @returns {String}
*/
Balance.prototype.toString = function toString() {
return '<Balance'
+ ' unconfirmed=' + utils.btc(this.unconfirmed)
@ -2411,6 +2433,11 @@ Balance.prototype.toString = function toString() {
+ '>';
};
/**
* Inspect balance.
* @param {String}
*/
Balance.prototype.inspect = function inspect() {
return this.toString();
};
@ -2418,6 +2445,8 @@ Balance.prototype.inspect = function inspect() {
/**
* Chain State
* @constructor
* @param {WalletID} wid
* @param {String} id
*/
function TXDBState(wid, id) {
@ -2430,6 +2459,11 @@ function TXDBState(wid, id) {
this.committed = false;
}
/**
* Clone the state.
* @returns {TXDBState}
*/
TXDBState.prototype.clone = function clone() {
var state = new TXDBState(this.wid, this.id);
state.tx = this.tx;
@ -2439,11 +2473,21 @@ TXDBState.prototype.clone = function clone() {
return state;
};
/**
* Commit and serialize state.
* @returns {Buffer}
*/
TXDBState.prototype.commit = function commit() {
this.committed = true;
return this.toRaw();
};
/**
* Convert state to a balance object.
* @returns {Balance}
*/
TXDBState.prototype.toBalance = function toBalance() {
var balance = new Balance(this.wid, this.id, -1);
balance.unconfirmed = this.unconfirmed;
@ -2451,6 +2495,11 @@ TXDBState.prototype.toBalance = function toBalance() {
return balance;
};
/**
* Serialize state.
* @returns {Buffer}
*/
TXDBState.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
@ -2465,6 +2514,13 @@ TXDBState.prototype.toRaw = function toRaw(writer) {
return p;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
* @returns {TXDBState}
*/
TXDBState.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
this.tx = p.readU53();
@ -2474,10 +2530,22 @@ TXDBState.prototype.fromRaw = function fromRaw(data) {
return this;
};
/**
* Instantiate txdb state from serialized data.
* @param {Buffer} data
* @returns {TXDBState}
*/
TXDBState.fromRaw = function fromRaw(wid, id, data) {
return new TXDBState(wid, id).fromRaw(data);
};
/**
* Convert state to a more json-friendly object.
* @param {Boolean?} minimal
* @returns {Object}
*/
TXDBState.prototype.toJSON = function toJSON(minimal) {
return {
wid: !minimal ? this.wid : undefined,
@ -2489,28 +2557,20 @@ TXDBState.prototype.toJSON = function toJSON(minimal) {
};
};
/**
* Inspect the state.
* @returns {Object}
*/
TXDBState.prototype.inspect = function inspect() {
return this.toJSON();
};
/*
* Helpers
*/
function Orphan(tx, i) {
this.tx = tx;
this.hash = tx.hash('hex');
this.index = i;
}
function cmp(a, b) {
return a - b;
}
/**
* Transaction Details
* @constructor
* @param {PathInfo} info
* @param {TXDB} txdb
* @param {TX} tx
*/
function Details(txdb, tx) {
@ -2538,6 +2598,11 @@ function Details(txdb, tx) {
this.init();
}
/**
* Initialize transaction details.
* @private
*/
Details.prototype.init = function init() {
var i, input, output, member;
@ -2557,6 +2622,13 @@ Details.prototype.init = function init() {
}
};
/**
* Add necessary info to input member.
* @param {Number} i
* @param {Path} path
* @param {Coin} coin
*/
Details.prototype.setInput = function setInput(i, path, coin) {
var member = this.inputs[i];
@ -2571,6 +2643,12 @@ Details.prototype.setInput = function setInput(i, path, coin) {
}
};
/**
* Add necessary info to output member.
* @param {Number} i
* @param {Path} path
*/
Details.prototype.setOutput = function setOutput(i, path) {
var member = this.outputs[i];
@ -2644,8 +2722,12 @@ DetailsMember.prototype.toJSON = function toJSON(network) {
};
/**
* Credit
* Credit (wrapped coin)
* @constructor
* @param {Coin} coin
* @param {Boolean?} spent
* @property {Coin} coin
* @property {Boolean} spent
*/
function Credit(coin, spent) {
@ -2656,6 +2738,12 @@ function Credit(coin, spent) {
this.spent = spent || false;
}
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
Credit.prototype.fromRaw = function fromRaw(data) {
var p = BufferReader(data);
this.coin.fromRaw(p);
@ -2663,10 +2751,21 @@ Credit.prototype.fromRaw = function fromRaw(data) {
return this;
};
/**
* Instantiate credit from serialized data.
* @param {Buffer} data
* @returns {Credit}
*/
Credit.fromRaw = function fromRaw(data) {
return new Credit().fromRaw(data);
};
/**
* Serialize credit.
* @returns {Buffer}
*/
Credit.prototype.toRaw = function toRaw(writer) {
var p = BufferWriter(writer);
@ -2679,16 +2778,45 @@ Credit.prototype.toRaw = function toRaw(writer) {
return p;
};
/**
* Inject properties from tx object.
* @private
* @param {TX} tx
* @param {Number} i
* @returns {Credit}
*/
Credit.prototype.fromTX = function fromTX(tx, i) {
this.coin.fromTX(tx, i);
this.spent = false;
return this;
};
/**
* Instantiate credit from transaction.
* @param {TX} tx
* @param {Number} i
* @returns {Credit}
*/
Credit.fromTX = function fromTX(tx, i) {
return new Credit().fromTX(tx, i);
};
/*
* Helpers
*/
function Orphan(tx, i) {
this.tx = tx;
this.hash = tx.hash('hex');
this.index = i;
}
function cmp(a, b) {
return a - b;
}
/*
* Expose
*/

View File

@ -1616,9 +1616,8 @@ Wallet.prototype.getOutputPaths = co(function* getOutputPaths(tx) {
* Sync address depths based on a transaction's outputs.
* This is used for deriving new addresses when
* a confirmed transaction is seen.
* @param {PathInfo} info
* @returns {Promise} - Returns Boolean
* (true if new addresses were allocated).
* @param {Details} details
* @returns {Promise}
*/
Wallet.prototype.syncOutputDepth = co(function* syncOutputDepth(details) {