more mapping.
This commit is contained in:
parent
ae7bbeb065
commit
90769d7e1a
@ -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
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user