more mapping.

This commit is contained in:
Christopher Jeffrey 2016-06-27 04:58:56 -07:00
parent ae7bbeb065
commit 90769d7e1a
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 261 additions and 219 deletions

View File

@ -113,7 +113,7 @@ TXDB.prototype._testFilter = function _testFilter(addresses) {
/** /**
* Map a transactions' addresses to wallet IDs. * Map a transactions' addresses to wallet IDs.
* @param {TX} tx * @param {TX} tx
* @param {Function} callback - Returns [Error, {@link AddressMap}]. * @param {Function} callback - Returns [Error, {@link WalletMap}].
*/ */
TXDB.prototype.getMap = function getMap(tx, callback) { TXDB.prototype.getMap = function getMap(tx, callback) {
@ -133,8 +133,6 @@ TXDB.prototype.getMap = function getMap(tx, callback) {
map = WalletMap.fromTX(table, tx); map = WalletMap.fromTX(table, tx);
utils.print(map.toJSON());
return callback(null, map); return callback(null, map);
}); });
}; };
@ -1667,88 +1665,140 @@ TXDB.prototype.zap = function zap(id, age, callback, force) {
// }] // }]
// //
// What we need:
// Table: Above.
// All: uniqified paths by id/name
// Outputs: Technically uniqified by ID, but id/name works too.
// What they need (api):
// outputs: [
// // Sum of value:
// { value: 0, id: wallet-id, name: account, index: account }
// ]
/** /**
* WalletMap * WalletMap
* @constructor * @constructor
* @private * @private
*/ */
function WalletMap(table) { function WalletMap() {
if (!(this instanceof WalletMap))
return new WalletMap();
this.inputs = []; this.inputs = [];
this.outputs = []; this.outputs = [];
this.paths = []; this.accounts = [];
this.accounts = null;
this.wallets = [];
this.table = null; this.table = null;
} }
WalletMap.prototype.fromTX = function fromTX(table, tx) { WalletMap.prototype.fromTX = function fromTX(table, tx) {
var keys = Object.keys(table); var i, members, input, output, key;
var i, input, output, hash, members, member;
var j, key, paths, path;
// Flatten paths and push all of // This is a scary function, but what it is
// them onto the `paths` array. // designed to do is uniqify inputs and
for (i = 0; i < keys.length; i++) { // outputs by account. This is easier said
key = keys[i]; // than done due to two facts: transactions
paths = table[key]; // can have multiple outputs with the same
for (j = 0; j < paths.length; j++) { // address, and wallets can have multiple
path = paths[j]; // accounts with the same address. On top
this.wallets.push(path.id); // of that, it will calculate the total
this.paths.push(path); // value sent to or received from each
// account.
function insert(vector, target) {
var i, io, hash, members, member;
var j, paths, path, key, address, hashes;
// Keeps track of unique addresses.
hashes = {};
// Maps address hashes to members.
members = {};
for (i = 0; i < vector.length; i++) {
io = vector[i];
address = io.getAddress();
if (!address)
continue;
hash = address.getHash('hex');
// Get all paths for this address.
paths = table[hash];
for (j = 0; j < paths.length; j++) {
path = paths[j];
key = path.toKey();
member = members[key];
// We no doubt already created a member
// for this account, and not only that,
// we're guaranteed to be on a different
// input/output due to the fact that we
// add the address hash after this loop
// completes. Now we can update the value.
if (hashes[hash]) {
assert(member);
if (io.coin)
member.value += io.coin.value;
else if (io.value)
member.value += io.value;
continue;
}
// Create a member for this account.
assert(!member);
member = MapMember.fromPath(path);
// Set the _initial_ value.
if (io.coin)
member.value = io.coin.value;
else if (io.value)
member.value = io.value;
// Add the address to the path object
// and push onto the member's paths.
// We only do this during instantiation,
// since paths are just as unique as
// addresses.
path.address = address;
member.paths.push(path);
// Remember it by wallet id / account
// name so we can update the value later.
members[key] = member;
// Push onto _our_ input/output array.
target.push(member);
}
// Update this guy last so the above if
// clause does not return true while
// we're still iterating over paths.
hashes[hash] = true;
}
}
// Finally, we convert both inputs
// and outputs to map members.
insert(tx.inputs, this.inputs);
insert(tx.outputs, this.outputs);
// Combine both input and output map
// members and uniqify them by account.
members = {};
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
key = input.toKey();
if (!members[key]) {
members[key] = true;
this.accounts.push(input);
}
}
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
key = output.toKey();
if (!members[key]) {
members[key] = true;
this.accounts.push(output);
} }
} }
this.wallets = utils.uniq(this.wallets);
this.accounts = uniq(this.paths);
this.table = table; this.table = table;
members = {};
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
hash = input.getHash('hex');
if (!hash)
continue;
member = members[hash];
if (!member) {
member = MapMember.fromMember(table, input);
members[hash] = member;
this.inputs.push(member);
continue;
}
if (input.coin)
member.value += input.coin.value;
}
members = {};
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
hash = output.getHash('hex');
member = members[hash];
if (!hash)
continue;
if (!member) {
member = MapMember.fromMember(table, output);
members[hash] = member;
this.outputs.push(member);
continue;
}
member.value += output.value;
}
return this; return this;
}; };
@ -1779,58 +1829,82 @@ WalletMap.prototype.toJSON = function toJSON() {
outputs: this.outputs.map(function(output) { outputs: this.outputs.map(function(output) {
return output.toJSON(); return output.toJSON();
}), }),
paths: this.paths.map(function(path) {
return path.toJSON();
}),
accounts: this.accounts.map(function(path) { accounts: this.accounts.map(function(path) {
return path.toAccount(); return path.toKey();
}), })
wallets: this.wallets
}; };
}; };
WalletMap.prototype.fromJSON = function fromJSON(json) { WalletMap.prototype.fromJSON = function fromJSON(json) {
var table = {}; var table = {};
var i, account, path, input, output, hash; var i, j, account, input, output, path;
var hash, paths, hashes, accounts, values, key;
for (i = 0; i < json.inputs.length; i++) { for (i = 0; i < json.inputs.length; i++) {
input = json.inputs[i]; input = json.inputs[i];
input = MapMember.fromJSON(input); input = MapMember.fromJSON(input);
hash = input.getHash('hex');
table[hash] = input.paths;
this.inputs.push(input); this.inputs.push(input);
for (j = 0; j < input.paths.length; j++) {
path = input.paths[j];
path.id = input.id;
path.name = input.name;
path.account = input.account;
hash = path.address.getHash('hex');
if (!table[hash])
table[hash] = [];
table[hash].push(path);
}
} }
for (i = 0; i < json.outputs.length; i++) { for (i = 0; i < json.outputs.length; i++) {
output = json.outputs[i]; output = json.outputs[i];
output = MapMember.fromJSON(output); output = MapMember.fromJSON(output);
hash = output.getHash('hex');
if (!table[hash])
table[hash] = output.paths;
this.outputs.push(output); this.outputs.push(output);
} for (j = 0; j < output.paths.length; j++) {
path = output.paths[j];
for (i = 0; i < json.paths.length; i++) { path.id = output.id;
path = json.paths[i]; path.name = output.name;
this.paths.push(bcoin.path.fromJSON(path)); path.account = output.account;
hash = path.address.getHash('hex');
if (!table[hash])
table[hash] = [];
table[hash].push(path);
}
} }
for (i = 0; i < json.accounts.length; i++) { for (i = 0; i < json.accounts.length; i++) {
account = json.accounts[i]; account = json.accounts[i];
this.accounts.push(bcoin.path.fromAccount(account)); this.accounts.push(bcoin.path.fromKey(account));
}
// We need to rebuild to address->paths table.
hashes = Object.keys(table);
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
paths = table[hash];
values = [];
accounts = {};
for (j = 0; j < paths.length; j++) {
path = paths[j];
key = path.toKey();
if (!accounts[key]) {
accounts[key] = true;
values.push(path);
}
}
table[hash] = values;
} }
this.wallets = json.wallets;
this.table = table; this.table = table;
return this; return this;
}; };
WalletMap.fromJSON = function fromJSON(json) { WalletMap.fromJSON = function fromJSON(json) {
return new MapMember({}).fromJSON(json); return new WalletMap().fromJSON(json);
}; };
/** /**
* MapMember * MapMember
* @constructor * @constructor
@ -1838,49 +1912,45 @@ WalletMap.fromJSON = function fromJSON(json) {
*/ */
function MapMember() { function MapMember() {
this.value = 0; if (!(this instanceof MapMember))
this.address = null; return new MapMember();
this.id = null;
this.name = null;
this.account = 0;
this.paths = []; this.paths = [];
this.accounts = []; this.value = 0;
this.wallets = [];
} }
MapMember.prototype.toKey = function toKey() {
return this.id + '/' + this.name + ':' + this.account;
};
MapMember.prototype.toJSON = function toJSON() { MapMember.prototype.toJSON = function toJSON() {
return { return {
value: utils.btc(this.value), id: this.id,
address: this.address name: this.name,
? this.address.toBase58() account: this.account,
: null,
hash: this.address
? this.address.getHash('hex')
: null,
paths: this.paths.map(function(path) { paths: this.paths.map(function(path) {
return path.toJSON(); return path.toCompact();
}), }),
accounts: this.accounts.map(function(path) { value: utils.btc(this.value)
return path.toAccount();
}),
wallets: this.wallets
}; };
}; };
MapMember.prototype.fromJSON = function fromJSON(json) { MapMember.prototype.fromJSON = function fromJSON(json) {
var i, account, path; var i, account, path;
this.value = utils.satoshi(json.value); this.id = json.id;
this.address = bcoin.address.fromBase58(json.address); this.name = json.name;
this.account = json.account;
for (i = 0; i < json.paths.length; i++) { for (i = 0; i < json.paths.length; i++) {
path = json.paths[i]; path = json.paths[i];
this.paths.push(bcoin.path.fromJSON(path)); this.paths.push(bcoin.path.fromCompact(path));
} }
for (i = 0; i < json.accounts.length; i++) { this.value = utils.satoshi(json.value);
account = json.accounts[i];
this.accounts.push(bcoin.path.fromAccount(account));
}
this.wallets = json.wallets;
return this; return this;
}; };
@ -1889,57 +1959,16 @@ MapMember.fromJSON = function fromJSON(json) {
return new MapMember().fromJSON(json); return new MapMember().fromJSON(json);
}; };
MapMember.fromMember = function fromMember(table, io) { MapMember.prototype.fromPath = function fromPath(path) {
var address = io.getAddress(); this.id = path.id;
var member = new MapMember(); this.name = path.name;
var i, paths; this.account = path.account;
return this;
if (io instanceof bcoin.input)
member.value = io.coin ? io.coin.value : 0;
else
member.value = io.value;
if (!address)
return member;
paths = table[address.getHash('hex')];
assert(paths);
member.address = address;
member.paths = paths;
for (i = 0; i < paths.length; i++)
member.wallets.push(paths[i].id);
member.accounts = uniq(member.paths);
member.wallets = utils.uniq(member.wallets);
return member;
}; };
/* MapMember.fromPath = function fromPath(path) {
* Helpers return new MapMember().fromPath(path);
*/ };
function uniq(obj) {
var uniq = {};
var out = [];
var i, key, value;
for (i = 0; i < obj.length; i++) {
value = obj[i];
key = value.id + '/' + value.account;
if (uniq[key])
continue;
uniq[key] = true;
out.push(value);
}
return out;
}
/* /*
* Expose * Expose

View File

@ -187,27 +187,22 @@ WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
var balances = {}; var balances = {};
utils.forEachSerial(map.outputs, function(output, next) { utils.forEachSerial(map.outputs, function(output, next) {
utils.forEachSerial(output.wallets, function(id, next) { var id = output.id;
if (self.listeners('balance').length === 0
&& !self.hasListener(id, 'balance')) {
return next();
}
if (balances[id] != null) if (self.listeners('balance').length === 0
return next(); && !self.hasListener(id, 'balance')) {
return next();
}
self.getBalance(id, function(err, balance) { if (balances[id] != null)
if (err) return next();
return next(err);
balances[id] = balance; self.getBalance(id, function(err, balance) {
next();
});
}, function(err) {
if (err) if (err)
return next(err); return next(err);
balances[id] = balance;
next(); next();
}); });
}, function(err) { }, function(err) {
@ -221,18 +216,12 @@ WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
WalletDB.prototype.syncOutputs = function syncOutputs(tx, map, callback) { WalletDB.prototype.syncOutputs = function syncOutputs(tx, map, callback) {
var self = this; var self = this;
utils.forEachSerial(map.outputs, function(output, next) { utils.forEachSerial(map.outputs, function(output, next) {
utils.forEachSerial(output.wallets, function(id, next) { var id = output.id;
self.syncOutputDepth(id, tx, function(err, receive, change) { self.syncOutputDepth(id, tx, function(err, receive, change) {
if (err)
return next(err);
self.fire(id, 'address', receive, change);
self.emit('address', receive, change, map);
next();
});
}, function(err) {
if (err) if (err)
return next(err); return next(err);
self.fire(id, 'address', receive, change);
self.emit('address', receive, change, map);
next(); next();
}); });
}, callback); }, callback);
@ -1164,12 +1153,15 @@ WalletDB.prototype.getRedeem = function getRedeem(id, hash, callback) {
*/ */
function Path() { function Path() {
if (!(this instanceof Path))
return new Path();
this.id = null; this.id = null;
this.name = null; this.name = null;
this.account = 0; this.account = 0;
this.change = 0; this.change = 0;
this.index = 0; this.index = 0;
this.hash = null; this.address = null;
} }
Path.prototype.fromRaw = function fromRaw(data) { Path.prototype.fromRaw = function fromRaw(data) {
@ -1186,6 +1178,21 @@ Path.fromRaw = function fromRaw(data) {
return new Path().fromRaw(data); return new Path().fromRaw(data);
}; };
Path.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
p.writeVarString(this.id, 'utf8');
p.writeVarString(this.name, 'utf8');
p.writeU32(this.account);
p.writeU32(this.change);
p.writeU32(this.index);
if (!writer)
p = p.render();
return p;
};
Path.prototype.fromKeyRing = function fromKeyRing(id, address) { Path.prototype.fromKeyRing = function fromKeyRing(id, address) {
this.id = id; this.id = id;
this.name = address.name; this.name = address.name;
@ -1212,14 +1219,6 @@ Path.prototype.inspect = function() {
+ '>'; + '>';
}; };
Path.prototype.toAccount = function toAccount() {
return {
id: this.id,
name: this.name,
account: this.account
};
};
Path.prototype.toJSON = function toJSON() { Path.prototype.toJSON = function toJSON() {
return { return {
id: this.id, id: this.id,
@ -1228,19 +1227,6 @@ Path.prototype.toJSON = function toJSON() {
}; };
}; };
Path.prototype.fromAccount = function fromAccount(json) {
this.id = json.id;
this.name = json.name;
this.account = json.account;
this.change = 0;
this.index = 0;
return this;
};
Path.fromAccount = function fromAccount(json) {
return new Path().fromAccount(json);
};
Path.prototype.fromJSON = function fromJSON(json) { Path.prototype.fromJSON = function fromJSON(json) {
var indexes = bcoin.hd.parsePath(json.path, constants.hd.MAX_INDEX); var indexes = bcoin.hd.parsePath(json.path, constants.hd.MAX_INDEX);
@ -1261,19 +1247,46 @@ Path.fromJSON = function fromJSON(json) {
return new Path().fromJSON(json); return new Path().fromJSON(json);
}; };
Path.prototype.toRaw = function toRaw(writer) { Path.prototype.toKey = function toKey() {
var p = new BufferWriter(writer); return this.id + '/' + this.name + ':' + this.account;
};
p.writeVarString(this.id, 'utf8'); Path.prototype.fromKey = function fromKey(key) {
p.writeVarString(this.name, 'utf8'); var parts = key.split('/');
p.writeU32(this.account); this.id = parts[0];
p.writeU32(this.change); parts = parts[1].split(':');
p.writeU32(this.index); this.name = parts[0];
this.account = +parts[1];
return this;
};
if (!writer) Path.fromKey = function fromKey(json) {
p = p.render(); return new Path().fromKey(key);
};
return p; Path.prototype.toCompact = function toCompact() {
return {
path: 'm/' + this.change + '/' + this.index,
address: this.address ? this.address.toBase58() : null
};
};
Path.prototype.fromCompact = function fromCompact(json) {
var indexes = bcoin.hd.parsePath(json.path, constants.hd.MAX_INDEX);
assert(indexes.length === 2);
this.change = indexes[0];
this.index = indexes[1];
this.address = json.address
? bcoin.address.fromBase58(json.address)
: null;
return this;
};
Path.fromCompact = function fromCompact(json) {
return new Path().fromCompact(json);
}; };
/* /*