From ae7bbeb0653db2c642d7239f000ef0b027341272 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 27 Jun 2016 03:07:43 -0700 Subject: [PATCH] wallet work. --- lib/bcoin/env.js | 1 + lib/bcoin/txdb.js | 448 ++++++++++++++++++++++++++---------------- lib/bcoin/walletdb.js | 47 ++++- 3 files changed, 321 insertions(+), 175 deletions(-) diff --git a/lib/bcoin/env.js b/lib/bcoin/env.js index 58fae1d7..eba6d24a 100644 --- a/lib/bcoin/env.js +++ b/lib/bcoin/env.js @@ -181,6 +181,7 @@ function Environment(options) { this.wallet = require('./wallet'); this.account = this.wallet.Account; this.walletdb = require('./walletdb'); + this.path = this.walletdb.Path; this.provider = this.walletdb.Provider; this.peer = require('./peer'); this.pool = require('./pool'); diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index d7ec7cc4..f850cfee 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -110,178 +110,6 @@ TXDB.prototype._testFilter = function _testFilter(addresses) { return false; }; -// Each address can potentially map to multiple -// accounts and wallets due to the fact that -// multisig accounts can have shared addresses. -// An address could map to 2 accounts on different -// wallets, or 2 accounts on the same wallet! -// In summary, bitcoin is hard. Use Bobchain instead. -// -// Table: -// [address-hash] -> [array of Path objects] -// '1edc6b6858fd12c64b26d8bd1e0e50d44b5bafb9': -// [Path { -// id: 'WLTZ3f5mMBsgWr1TcLzAdtLD8pkLcmWuBfPt', -// name: 'default', -// account: 0, -// change: 0, -// index: 0 -// }] -// - -// 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 } -// ] - -function WalletMap(table) { - var i, j, keys, key, paths, path; - - this.inputs = []; - this.outputs = []; - this.paths = []; - this.accounts = null; - this.wallets = []; - this.table = null; - - keys = Object.keys(table); - - // 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.wallets = utils.uniq(this.wallets); - this.accounts = uniq(this.paths); - this.table = table; -} - -WalletMap.fromTX = function fromTX(table, tx) { - var map = new WalletMap(table); - var i, input, output; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - map.inputs.push(MapMember.fromMember(table, input)); - } - - for (i = 0; i < tx.outputs.length; i++) { - output = tx.outputs[i]; - map.outputs.push(MapMember.fromMember(table, output)); - } - - return map; -}; - -WalletMap.prototype.hasPaths = function hasPaths(address) { - var paths; - - if (!address) - return false; - - paths = this.table[address]; - - return paths && paths.length !== 0; -}; - -WalletMap.prototype.getPaths = function getPaths(address) { - return this.table[address]; -}; - -WalletMap.prototype.toJSON = function toJSON() { - return { - inputs: this.inputs.map(function(input) { - return input.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 { - id: path.id, - name: path.name, - account: path.account - }; - }), - wallets: this.wallets - }; -}; - -function MapMember() { - this.value = 0; - this.address = null; - this.paths = []; - this.accounts = []; - this.wallets = []; -} - -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, - paths: this.paths.map(function(path) { - return path.toJSON(); - }), - accounts: this.accounts.map(function(path) { - return { - id: path.id, - name: path.name, - account: path.account - }; - }), - wallets: this.wallets - }; -}; - -MapMember.fromMember = function fromMember(table, io) { - var address = io.getAddress(); - var member = new MapMember(); - var i, paths; - - member.value = io.coin - ? io.coin.value - : io.value || 0; - - 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; -}; - /** * Map a transactions' addresses to wallet IDs. * @param {TX} tx @@ -305,6 +133,8 @@ TXDB.prototype.getMap = function getMap(tx, callback) { map = WalletMap.fromTX(table, tx); + utils.print(map.toJSON()); + return callback(null, map); }); }; @@ -1814,6 +1644,280 @@ TXDB.prototype.zap = function zap(id, age, callback, force) { }); }; +/* + * Address->Wallet Mapping + */ + +// Each address can potentially map to multiple +// accounts and wallets due to the fact that +// multisig accounts can have shared addresses. +// An address could map to 2 accounts on different +// wallets, or 2 accounts on the same wallet! +// In summary, bitcoin is hard. Use Bobchain instead. +// +// Table: +// [address-hash] -> [array of Path objects] +// '1edc6b6858fd12c64b26d8bd1e0e50d44b5bafb9': +// [Path { +// id: 'WLTZ3f5mMBsgWr1TcLzAdtLD8pkLcmWuBfPt', +// name: 'default', +// account: 0, +// change: 0, +// index: 0 +// }] +// + +// 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) { + this.inputs = []; + this.outputs = []; + this.paths = []; + this.accounts = null; + this.wallets = []; + 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; + + // 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.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; +}; + +WalletMap.fromTX = function fromTX(table, tx) { + return new WalletMap().fromTX(table, tx); +}; + +WalletMap.prototype.hasPaths = function hasPaths(address) { + var paths; + + if (!address) + return false; + + paths = this.table[address]; + + return paths && paths.length !== 0; +}; + +WalletMap.prototype.getPaths = function getPaths(address) { + return this.table[address]; +}; + +WalletMap.prototype.toJSON = function toJSON() { + return { + inputs: this.inputs.map(function(input) { + return input.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 + }; +}; + +WalletMap.prototype.fromJSON = function fromJSON(json) { + var table = {}; + var i, account, path, input, output, hash; + + 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 (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 (i = 0; i < json.accounts.length; i++) { + account = json.accounts[i]; + this.accounts.push(bcoin.path.fromAccount(account)); + } + + this.wallets = json.wallets; + this.table = table; + + return this; +}; + +WalletMap.fromJSON = function fromJSON(json) { + return new MapMember({}).fromJSON(json); +}; + + +/** + * MapMember + * @constructor + * @private + */ + +function MapMember() { + this.value = 0; + this.address = null; + this.paths = []; + this.accounts = []; + this.wallets = []; +} + +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, + paths: this.paths.map(function(path) { + return path.toJSON(); + }), + accounts: this.accounts.map(function(path) { + return path.toAccount(); + }), + wallets: this.wallets + }; +}; + +MapMember.prototype.fromJSON = function fromJSON(json) { + var i, account, path; + + this.value = utils.satoshi(json.value); + this.address = bcoin.address.fromBase58(json.address); + + for (i = 0; i < json.paths.length; i++) { + path = json.paths[i]; + this.paths.push(bcoin.path.fromJSON(path)); + } + + for (i = 0; i < json.accounts.length; i++) { + account = json.accounts[i]; + this.accounts.push(bcoin.path.fromAccount(account)); + } + + this.wallets = json.wallets; + + return this; +}; + +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; +}; + /* * Helpers */ diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 35a6952c..400948b0 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -1160,6 +1160,7 @@ WalletDB.prototype.getRedeem = function getRedeem(id, hash, callback) { /** * Path * @constructor + * @private */ function Path() { @@ -1211,16 +1212,55 @@ 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, name: this.name, - account: this.account, - change: this.change, - index: this.index + path: this.toPath() }; }; +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); + + assert(indexes.length === 3); + assert(indexes[0] >= 0); + indexes[0] -= constants.hd.HARDENED; + + this.id = json.id; + this.name = json.name; + this.account = indexes[0]; + this.change = indexes[1]; + this.index = indexes[2]; + + return this; +}; + +Path.fromJSON = function fromJSON(json) { + return new Path().fromJSON(json); +}; + Path.prototype.toRaw = function toRaw(writer) { var p = new BufferWriter(writer); @@ -1277,5 +1317,6 @@ function isAlpha(key) { */ exports = WalletDB; +exports.Path = Path; module.exports = exports;