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.
* @param {TX} tx
* @param {Function} callback - Returns [Error, {@link AddressMap}].
* @param {Function} callback - Returns [Error, {@link WalletMap}].
*/
TXDB.prototype.getMap = function getMap(tx, callback) {
@ -133,8 +133,6 @@ TXDB.prototype.getMap = function getMap(tx, callback) {
map = WalletMap.fromTX(table, tx);
utils.print(map.toJSON());
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
* @constructor
* @private
*/
function WalletMap(table) {
function WalletMap() {
if (!(this instanceof WalletMap))
return new WalletMap();
this.inputs = [];
this.outputs = [];
this.paths = [];
this.accounts = null;
this.wallets = [];
this.accounts = [];
this.table = null;
}
WalletMap.prototype.fromTX = function fromTX(table, tx) {
var keys = Object.keys(table);
var i, input, output, hash, members, member;
var j, key, paths, path;
var i, members, input, output, key;
// Flatten paths and push all of
// them onto the `paths` array.
for (i = 0; i < keys.length; i++) {
key = keys[i];
paths = table[key];
for (j = 0; j < paths.length; j++) {
path = paths[j];
this.wallets.push(path.id);
this.paths.push(path);
// This is a scary function, but what it is
// designed to do is uniqify inputs and
// outputs by account. This is easier said
// than done due to two facts: transactions
// can have multiple outputs with the same
// address, and wallets can have multiple
// accounts with the same address. On top
// of that, it will calculate the total
// 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;
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;
};
@ -1779,58 +1829,82 @@ WalletMap.prototype.toJSON = function toJSON() {
outputs: this.outputs.map(function(output) {
return output.toJSON();
}),
paths: this.paths.map(function(path) {
return path.toJSON();
}),
accounts: this.accounts.map(function(path) {
return path.toAccount();
}),
wallets: this.wallets
return path.toKey();
})
};
};
WalletMap.prototype.fromJSON = function fromJSON(json) {
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++) {
input = json.inputs[i];
input = MapMember.fromJSON(input);
hash = input.getHash('hex');
table[hash] = input.paths;
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++) {
output = json.outputs[i];
output = MapMember.fromJSON(output);
hash = output.getHash('hex');
if (!table[hash])
table[hash] = output.paths;
this.outputs.push(output);
}
for (i = 0; i < json.paths.length; i++) {
path = json.paths[i];
this.paths.push(bcoin.path.fromJSON(path));
for (j = 0; j < output.paths.length; j++) {
path = output.paths[j];
path.id = output.id;
path.name = output.name;
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++) {
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;
return this;
};
WalletMap.fromJSON = function fromJSON(json) {
return new MapMember({}).fromJSON(json);
return new WalletMap().fromJSON(json);
};
/**
* MapMember
* @constructor
@ -1838,49 +1912,45 @@ WalletMap.fromJSON = function fromJSON(json) {
*/
function MapMember() {
this.value = 0;
this.address = null;
if (!(this instanceof MapMember))
return new MapMember();
this.id = null;
this.name = null;
this.account = 0;
this.paths = [];
this.accounts = [];
this.wallets = [];
this.value = 0;
}
MapMember.prototype.toKey = function toKey() {
return this.id + '/' + this.name + ':' + this.account;
};
MapMember.prototype.toJSON = function toJSON() {
return {
value: utils.btc(this.value),
address: this.address
? this.address.toBase58()
: null,
hash: this.address
? this.address.getHash('hex')
: null,
id: this.id,
name: this.name,
account: this.account,
paths: this.paths.map(function(path) {
return path.toJSON();
return path.toCompact();
}),
accounts: this.accounts.map(function(path) {
return path.toAccount();
}),
wallets: this.wallets
value: utils.btc(this.value)
};
};
MapMember.prototype.fromJSON = function fromJSON(json) {
var i, account, path;
this.value = utils.satoshi(json.value);
this.address = bcoin.address.fromBase58(json.address);
this.id = json.id;
this.name = json.name;
this.account = json.account;
for (i = 0; i < json.paths.length; 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++) {
account = json.accounts[i];
this.accounts.push(bcoin.path.fromAccount(account));
}
this.wallets = json.wallets;
this.value = utils.satoshi(json.value);
return this;
};
@ -1889,57 +1959,16 @@ MapMember.fromJSON = function fromJSON(json) {
return new MapMember().fromJSON(json);
};
MapMember.fromMember = function fromMember(table, io) {
var address = io.getAddress();
var member = new MapMember();
var i, paths;
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.prototype.fromPath = function fromPath(path) {
this.id = path.id;
this.name = path.name;
this.account = path.account;
return this;
};
/*
* Helpers
*/
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;
}
MapMember.fromPath = function fromPath(path) {
return new MapMember().fromPath(path);
};
/*
* Expose

View File

@ -187,27 +187,22 @@ WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
var balances = {};
utils.forEachSerial(map.outputs, function(output, next) {
utils.forEachSerial(output.wallets, function(id, next) {
if (self.listeners('balance').length === 0
&& !self.hasListener(id, 'balance')) {
return next();
}
var id = output.id;
if (balances[id] != null)
return next();
if (self.listeners('balance').length === 0
&& !self.hasListener(id, 'balance')) {
return next();
}
self.getBalance(id, function(err, balance) {
if (err)
return next(err);
if (balances[id] != null)
return next();
balances[id] = balance;
next();
});
}, function(err) {
self.getBalance(id, function(err, balance) {
if (err)
return next(err);
balances[id] = balance;
next();
});
}, function(err) {
@ -221,18 +216,12 @@ WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
WalletDB.prototype.syncOutputs = function syncOutputs(tx, map, callback) {
var self = this;
utils.forEachSerial(map.outputs, function(output, next) {
utils.forEachSerial(output.wallets, function(id, next) {
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) {
var id = output.id;
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();
});
}, callback);
@ -1164,12 +1153,15 @@ WalletDB.prototype.getRedeem = function getRedeem(id, hash, callback) {
*/
function Path() {
if (!(this instanceof Path))
return new Path();
this.id = null;
this.name = null;
this.account = 0;
this.change = 0;
this.index = 0;
this.hash = null;
this.address = null;
}
Path.prototype.fromRaw = function fromRaw(data) {
@ -1186,6 +1178,21 @@ Path.fromRaw = function 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) {
this.id = id;
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() {
return {
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) {
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);
};
Path.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
Path.prototype.toKey = function toKey() {
return this.id + '/' + this.name + ':' + this.account;
};
p.writeVarString(this.id, 'utf8');
p.writeVarString(this.name, 'utf8');
p.writeU32(this.account);
p.writeU32(this.change);
p.writeU32(this.index);
Path.prototype.fromKey = function fromKey(key) {
var parts = key.split('/');
this.id = parts[0];
parts = parts[1].split(':');
this.name = parts[0];
this.account = +parts[1];
return this;
};
if (!writer)
p = p.render();
Path.fromKey = function fromKey(json) {
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);
};
/*