walletdb. txdb. webhooks.
This commit is contained in:
parent
ad8090dc7c
commit
e6048e85c8
@ -844,7 +844,7 @@ HDPrivateKey.prototype.toJSON = function toJSON(passphrase) {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
json.xpubkey = this.hd.xpubkey;
|
json.xpubkey = this.xpubkey;
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -182,72 +182,94 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
// Create/get wallet
|
// Create/get wallet
|
||||||
this.post('/wallet/:id', function(req, res, next, send) {
|
this.post('/wallet/:id', function(req, res, next, send) {
|
||||||
req.body.id = req.params.id;
|
req.body.id = req.params.id;
|
||||||
self.node.walletdb.createJSON(req.body.id, req.body, function(err, json) {
|
self.node.walletdb.create(req.body, function(err, wallet) {
|
||||||
|
var wallet;
|
||||||
|
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
if (!json)
|
if (!wallet)
|
||||||
return send(404);
|
return send(404);
|
||||||
|
|
||||||
|
json = wallet.toJSON();
|
||||||
|
wallet.destroy();
|
||||||
|
|
||||||
send(200, json);
|
send(200, json);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update wallet / sync address depth
|
// Update wallet / sync address depth
|
||||||
this.put('/wallet/:id', function(req, res, next, send) {
|
this.put('/wallet/:id', function(req, res, next, send) {
|
||||||
req.body.id = req.params.id;
|
var id = req.params.id;
|
||||||
self.node.walletdb.saveJSON(req.body.id, req.body, function(err, json) {
|
var receive = req.body.receiveDepth;
|
||||||
|
var change = req.body.changeDepth;
|
||||||
|
self.node.walletdb.syncDepth(id, receive, change, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
if (!json)
|
if (!json)
|
||||||
return send(404);
|
return send(404);
|
||||||
|
|
||||||
send(200, json);
|
send(200, { success: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wallet Balance
|
||||||
|
this.get('/wallet/:id/balance', function(req, res, next, send) {
|
||||||
|
self.node.walletdb.getBalance(req.params.id, function(err, balance) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
if (!coins.length)
|
||||||
|
return send(404);
|
||||||
|
|
||||||
|
send(200, { balance: utils.btc(balance) });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wallet UTXOs
|
// Wallet UTXOs
|
||||||
this.get('/wallet/:id/utxo', function(req, res, next, send) {
|
this.get('/wallet/:id/utxo', function(req, res, next, send) {
|
||||||
self.node.walletdb.getJSON(req.params.id, function(err, json) {
|
self.node.walletdb.getUnspent(req.params.id, function(err, coins) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
if (!json)
|
if (!coins.length)
|
||||||
return send(404);
|
return send(404);
|
||||||
|
|
||||||
self.node.getCoinByAddress(Object.keys(json.addressMap), function(err, coins) {
|
send(200, coins.map(function(coin) { return coin.toJSON(); }));
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
|
|
||||||
if (!coins.length)
|
|
||||||
return send(404);
|
|
||||||
|
|
||||||
send(200, coins.map(function(coin) { return coin.toJSON(); }));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wallet TXs
|
// Wallet TXs
|
||||||
this.get('/wallet/:id/tx', function(req, res, next, send) {
|
this.get('/wallet/:id/tx', function(req, res, next, send) {
|
||||||
self.node.walletdb.getJSON(req.params.id, function(err, json) {
|
self.node.walletdb.getAll(req.params.id, function(err, txs) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
if (!json)
|
if (!txs.length)
|
||||||
return send(404);
|
return send(404);
|
||||||
|
|
||||||
self.node.getTXByAddress(Object.keys(json.addressMap), function(err, txs) {
|
send(200, txs.map(function(tx) { return tx.toJSON(); }));
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
|
|
||||||
if (!txs.length)
|
|
||||||
return send(404);
|
|
||||||
|
|
||||||
send(200, coins.map(function(tx) { return tx.toJSON(); }));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wallet Pending TXs
|
||||||
|
this.get('/wallet/:id/pending', function(req, res, next, send) {
|
||||||
|
self.node.walletdb.getPending(req.params.id, function(err, txs) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
if (!txs.length)
|
||||||
|
return send(404);
|
||||||
|
|
||||||
|
send(200, txs.map(function(tx) { return tx.toJSON(); }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emit events for any wallet txs that come in.
|
||||||
|
this.node.on('wallet tx', function(tx, ids) {
|
||||||
|
self.sendWebhook({ tx: tx, ids: ids });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
HTTPServer.prototype.listen = function listen(port, host, callback) {
|
HTTPServer.prototype.listen = function listen(port, host, callback) {
|
||||||
@ -370,6 +392,42 @@ HTTPServer.prototype.del = function del(path, callback) {
|
|||||||
this.routes.del.push({ path: path, callback: callback });
|
this.routes.del.push({ path: path, callback: callback });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Send webhooks to notify other servers
|
||||||
|
// of incoming txs and wallet updates.
|
||||||
|
HTTPServer.prototype.sendWebhook = function sendWebhook(msg, callback) {
|
||||||
|
var request, body, secret, hmac;
|
||||||
|
|
||||||
|
callback = utils.ensure(callback);
|
||||||
|
|
||||||
|
if (!this.options.webhook)
|
||||||
|
return callback();
|
||||||
|
|
||||||
|
try {
|
||||||
|
request = require('request');
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
body = new Buffer(JSON.stringify(msg) + '\n', 'utf8');
|
||||||
|
secret = new Buffer(this.options.webhook.secret || '', 'utf8');
|
||||||
|
hmac = utils.sha512hmac(body, secret);
|
||||||
|
|
||||||
|
request({
|
||||||
|
method: 'POST',
|
||||||
|
uri: this.options.webhook.endpoint,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
'Content-Length': body.length + '',
|
||||||
|
'X-Bcoin-Hmac': hmac.toString('hex')
|
||||||
|
},
|
||||||
|
body: body
|
||||||
|
}, function(err, res, body) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helpers
|
* Helpers
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -81,7 +81,8 @@ Fullnode.prototype._init = function _init() {
|
|||||||
// and blockdb.
|
// and blockdb.
|
||||||
this.http = new bcoin.http(this, {
|
this.http = new bcoin.http(this, {
|
||||||
key: this.options.httpKey,
|
key: this.options.httpKey,
|
||||||
cert: this.options.httpCert
|
cert: this.options.httpCert,
|
||||||
|
webhook: this.options.webhook
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bind to errors
|
// Bind to errors
|
||||||
@ -97,12 +98,20 @@ Fullnode.prototype._init = function _init() {
|
|||||||
self.emit('error', err);
|
self.emit('error', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.walletdb.on('error', function(err) {
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
|
||||||
// Emit events for any TX we see that's
|
// Emit events for any TX we see that's
|
||||||
// is relevant to one of our wallets.
|
// is relevant to one of our wallets.
|
||||||
|
this.walletdb.on('wallet tx', function(tx, ids) {
|
||||||
|
self.emit('wallet tx', tx, ids);
|
||||||
|
});
|
||||||
|
|
||||||
this.on('tx', function(tx) {
|
this.on('tx', function(tx) {
|
||||||
self.walletdb.tx.addTX(tx, function(err, updated) {
|
self.walletdb.tx.addTX(tx, function(err) {
|
||||||
if (updated)
|
if (err)
|
||||||
self.emit('wallet tx', tx);
|
self.emit('error', err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ function Node(options) {
|
|||||||
this.pool = null;
|
this.pool = null;
|
||||||
this.chain = null;
|
this.chain = null;
|
||||||
this.miner = null;
|
this.miner = null;
|
||||||
this.profiler = null;
|
this.walletdb = null;
|
||||||
|
|
||||||
Node.global = this;
|
Node.global = this;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,18 +14,27 @@ var EventEmitter = require('events').EventEmitter;
|
|||||||
* TXPool
|
* TXPool
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function TXPool(prefix, db) {
|
function TXPool(prefix, db, options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (!(this instanceof TXPool))
|
if (!(this instanceof TXPool))
|
||||||
return new TXPool(wallet, txs);
|
return new TXPool(prefix, db, options);
|
||||||
|
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
|
if (!options)
|
||||||
|
options = {};
|
||||||
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.prefix = prefix || 'pool';
|
this.prefix = prefix || 'pool';
|
||||||
|
this.options = options;
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
this.jobs = [];
|
this.jobs = [];
|
||||||
|
|
||||||
|
if (options.addressFilter)
|
||||||
|
this._hasAddress = options.addressFilter;
|
||||||
|
|
||||||
|
this.setMaxListeners(Number.MAX_SAFE_INTEGER);
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.inherits(TXPool, EventEmitter);
|
utils.inherits(TXPool, EventEmitter);
|
||||||
@ -93,6 +102,7 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
|||||||
var p = this.prefix + '/';
|
var p = this.prefix + '/';
|
||||||
var hash = tx.hash('hex');
|
var hash = tx.hash('hex');
|
||||||
var updated = false;
|
var updated = false;
|
||||||
|
var own = false;
|
||||||
var batch;
|
var batch;
|
||||||
|
|
||||||
var unlock = this._lock(add, [tx, callback], force);
|
var unlock = this._lock(add, [tx, callback], force);
|
||||||
@ -112,6 +122,7 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
|||||||
batch = self.db.batch();
|
batch = self.db.batch();
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
own = true;
|
||||||
// Tricky - update the tx and coin in storage,
|
// Tricky - update the tx and coin in storage,
|
||||||
// and remove pending flag to mark as confirmed.
|
// and remove pending flag to mark as confirmed.
|
||||||
if (existing.ts === 0 && tx.ts !== 0) {
|
if (existing.ts === 0 && tx.ts !== 0) {
|
||||||
@ -217,6 +228,7 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
|||||||
return done(null, false);
|
return done(null, false);
|
||||||
|
|
||||||
updated = true;
|
updated = true;
|
||||||
|
own = true;
|
||||||
|
|
||||||
type = input.getType();
|
type = input.getType();
|
||||||
address = input.getAddress();
|
address = input.getAddress();
|
||||||
@ -250,6 +262,8 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
|||||||
if (!result)
|
if (!result)
|
||||||
return next();
|
return next();
|
||||||
|
|
||||||
|
own = true;
|
||||||
|
|
||||||
self.getTX(input.prevout.hash, function(err, result) {
|
self.getTX(input.prevout.hash, function(err, result) {
|
||||||
if (err)
|
if (err)
|
||||||
return done(err);
|
return done(err);
|
||||||
@ -305,6 +319,7 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
|||||||
|
|
||||||
key = hash + '/' + i;
|
key = hash + '/' + i;
|
||||||
coin = bcoin.coin(tx, i);
|
coin = bcoin.coin(tx, i);
|
||||||
|
own = true;
|
||||||
|
|
||||||
self.db.get(p + 'o/' + key, function(err, orphans) {
|
self.db.get(p + 'o/' + key, function(err, orphans) {
|
||||||
var some;
|
var some;
|
||||||
@ -400,6 +415,9 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
|||||||
if (err)
|
if (err)
|
||||||
return done(err);
|
return done(err);
|
||||||
|
|
||||||
|
if (!own)
|
||||||
|
return done(null, false);
|
||||||
|
|
||||||
batch.put(p + 't/t/' + hash, tx.toExtended());
|
batch.put(p + 't/t/' + hash, tx.toExtended());
|
||||||
if (tx.ts === 0)
|
if (tx.ts === 0)
|
||||||
batch.put(p + 'p/t/' + hash, new Buffer([]));
|
batch.put(p + 'p/t/' + hash, new Buffer([]));
|
||||||
@ -418,11 +436,12 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
|||||||
|
|
||||||
self.emit('tx', tx);
|
self.emit('tx', tx);
|
||||||
|
|
||||||
if (tx.ts !== 0)
|
if (updated) {
|
||||||
self.emit('confirmed', tx);
|
if (tx.ts !== 0)
|
||||||
|
self.emit('confirmed', tx);
|
||||||
|
|
||||||
if (updated)
|
|
||||||
self.emit('updated', tx);
|
self.emit('updated', tx);
|
||||||
|
}
|
||||||
|
|
||||||
return done(null, true);
|
return done(null, true);
|
||||||
});
|
});
|
||||||
@ -856,6 +875,8 @@ TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
|
txs = utils.uniqs(txs);
|
||||||
|
|
||||||
return callback(null, txs);
|
return callback(null, txs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,8 @@ function Wallet(options) {
|
|||||||
options.master = bcoin.hd.fromSeed();
|
options.master = bcoin.hd.fromSeed();
|
||||||
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
this.db = options.db || null;
|
||||||
|
this.tx = options.tx || null;
|
||||||
this.provider = options.provider || null;
|
this.provider = options.provider || null;
|
||||||
this.addresses = [];
|
this.addresses = [];
|
||||||
this.master = options.master || null;
|
this.master = options.master || null;
|
||||||
@ -87,6 +89,10 @@ function Wallet(options) {
|
|||||||
|
|
||||||
this.id = this.getID();
|
this.id = this.getID();
|
||||||
|
|
||||||
|
// Non-alphanumeric IDs will break leveldb sorting.
|
||||||
|
if (!/^[a-zA-Z0-9]$/.test(this.id))
|
||||||
|
throw new Error('Wallet IDs must be alphanumeric.');
|
||||||
|
|
||||||
this.addKey(this.accountKey);
|
this.addKey(this.accountKey);
|
||||||
|
|
||||||
(options.keys || []).forEach(function(key) {
|
(options.keys || []).forEach(function(key) {
|
||||||
@ -124,6 +130,63 @@ Wallet.prototype._init = function _init() {
|
|||||||
assert(this.receiveAddress);
|
assert(this.receiveAddress);
|
||||||
assert(!this.receiveAddress.change);
|
assert(!this.receiveAddress.change);
|
||||||
assert(this.changeAddress.change);
|
assert(this.changeAddress.change);
|
||||||
|
|
||||||
|
if (!this.tx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.tx.on('tx', this._onTX = function(tx) {
|
||||||
|
if (!self.ownInput(tx) && !self.ownOutput(tx))
|
||||||
|
return;
|
||||||
|
|
||||||
|
self.emit('tx', tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tx.on('updated', this._onUpdated = function(tx) {
|
||||||
|
if (!self.ownInput(tx) && !self.ownOutput(tx))
|
||||||
|
return;
|
||||||
|
|
||||||
|
self.emit('updated', tx);
|
||||||
|
|
||||||
|
if (!self.provider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self.getBalance(function(err, balance) {
|
||||||
|
if (err)
|
||||||
|
return self.emit('error', err);
|
||||||
|
|
||||||
|
self.emit('balance', balance);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tx.on('confirmed', this._onConfirmed = function(tx) {
|
||||||
|
if (!self.ownInput(tx) && !self.ownOutput(tx))
|
||||||
|
return;
|
||||||
|
|
||||||
|
self.syncOutputDepth(tx);
|
||||||
|
self.emit('confirmed', tx);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Wallet.prototype.destroy = function destroy() {
|
||||||
|
if (this.tx) {
|
||||||
|
if (this._onTX) {
|
||||||
|
this.tx.removeListener('tx', this._onTX);
|
||||||
|
delete this._onTX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._onUpdated) {
|
||||||
|
this.tx.removeListener('updated', this._onUpdated);
|
||||||
|
delete this._onUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._onConfirmed) {
|
||||||
|
this.tx.removeListener('confirmed', this._onConfirmed);
|
||||||
|
delete this._onConfirmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.db = null;
|
||||||
|
this.tx = null;
|
||||||
|
this.provider = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
Wallet.prototype.addKey = function addKey(key) {
|
Wallet.prototype.addKey = function addKey(key) {
|
||||||
@ -341,6 +404,10 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
|
|||||||
if (this.witness)
|
if (this.witness)
|
||||||
this.addressMap[address.getProgramAddress()] = data.path;
|
this.addressMap[address.getProgramAddress()] = data.path;
|
||||||
|
|
||||||
|
// Update the DB with the new address.
|
||||||
|
if (this.db)
|
||||||
|
this.db.update(this, address);
|
||||||
|
|
||||||
this.emit('add address', address);
|
this.emit('add address', address);
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
@ -473,27 +540,9 @@ Wallet.prototype.fillPrevout = function fillPrevout(tx, callback) {
|
|||||||
return this.provider.fillCoin(tx, callback);
|
return this.provider.fillCoin(tx, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
Wallet.prototype.sync = function sync(callback) {
|
Wallet.prototype.createTX = function createTX(options, outputs, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var tx;
|
||||||
callback = utils.ensure(callback);
|
|
||||||
|
|
||||||
this.getUnspent(function(err, unspent) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
unspent.forEach(function(coin) {
|
|
||||||
self.syncOutputDepth(coin);
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Wallet.prototype.tx = function _tx(options, outputs, callback) {
|
|
||||||
var self = this;
|
|
||||||
var tx = bcoin.mtx();
|
|
||||||
var target;
|
|
||||||
|
|
||||||
if (typeof outputs === 'function') {
|
if (typeof outputs === 'function') {
|
||||||
callback = outputs;
|
callback = outputs;
|
||||||
@ -508,6 +557,9 @@ Wallet.prototype.tx = function _tx(options, outputs, callback) {
|
|||||||
if (!Array.isArray(outputs))
|
if (!Array.isArray(outputs))
|
||||||
outputs = [outputs];
|
outputs = [outputs];
|
||||||
|
|
||||||
|
// Create mutable tx
|
||||||
|
tx = bcoin.mtx();
|
||||||
|
|
||||||
// Add the outputs
|
// Add the outputs
|
||||||
outputs.forEach(function(output) {
|
outputs.forEach(function(output) {
|
||||||
tx.addOutput(output);
|
tx.addOutput(output);
|
||||||
@ -521,20 +573,9 @@ Wallet.prototype.tx = function _tx(options, outputs, callback) {
|
|||||||
// Sort members a la BIP69
|
// Sort members a la BIP69
|
||||||
tx.sortMembers();
|
tx.sortMembers();
|
||||||
|
|
||||||
// Find the necessary locktime if there is
|
|
||||||
// a checklocktimeverify script in the unspents.
|
|
||||||
target = tx.getTargetLocktime();
|
|
||||||
|
|
||||||
// No target value. The unspents have an
|
|
||||||
// incompatible locktime type.
|
|
||||||
if (!target)
|
|
||||||
return callback(new Error('Incompatible locktime.'));
|
|
||||||
|
|
||||||
// Set the locktime to target value or
|
// Set the locktime to target value or
|
||||||
// `height - whatever` to avoid fee sniping.
|
// `height - whatever` to avoid fee sniping.
|
||||||
if (target.value > 0)
|
if (options.locktime != null)
|
||||||
tx.setLocktime(target.value);
|
|
||||||
else if (options.locktime != null)
|
|
||||||
tx.setLocktime(options.locktime);
|
tx.setLocktime(options.locktime);
|
||||||
else
|
else
|
||||||
tx.avoidFeeSniping();
|
tx.avoidFeeSniping();
|
||||||
@ -758,12 +799,10 @@ Wallet.prototype.sign = function sign(tx, type, index) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Wallet.prototype.addTX = function addTX(tx, callback) {
|
Wallet.prototype.addTX = function addTX(tx, callback) {
|
||||||
this.syncOutputDepth(tx);
|
if (!this.tx)
|
||||||
|
return callback(new Error('No transaction pool available.'));
|
||||||
|
|
||||||
if (!this.provider)
|
return this.tx.add(tx, callback);
|
||||||
return callback(new Error('No wallet provider available.'));
|
|
||||||
|
|
||||||
return this.provider.addTX(tx, callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Wallet.prototype.getAll = function getAll(callback) {
|
Wallet.prototype.getAll = function getAll(callback) {
|
||||||
@ -929,6 +968,10 @@ Wallet.prototype.toJSON = function toJSON() {
|
|||||||
receiveDepth: this.receiveDepth,
|
receiveDepth: this.receiveDepth,
|
||||||
changeDepth: this.changeDepth,
|
changeDepth: this.changeDepth,
|
||||||
master: this.master.toJSON(this.options.passphrase),
|
master: this.master.toJSON(this.options.passphrase),
|
||||||
|
// Store account key to:
|
||||||
|
// a. save ourselves derivations.
|
||||||
|
// b. allow deriving of addresses without decrypting the master key.
|
||||||
|
accountKey: this.accountKey.xpubkey,
|
||||||
addressMap: this.addressMap,
|
addressMap: this.addressMap,
|
||||||
keys: this.keys.map(function(key) {
|
keys: this.keys.map(function(key) {
|
||||||
return key.xpubkey;
|
return key.xpubkey;
|
||||||
@ -961,6 +1004,39 @@ Wallet._fromJSON = function _fromJSON(json, passphrase) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// For updating the address table quickly
|
||||||
|
// without decrypting the master key.
|
||||||
|
Wallet.sync = function sync(json, options) {
|
||||||
|
var master, wallet;
|
||||||
|
|
||||||
|
assert.equal(json.v, 3);
|
||||||
|
assert.equal(json.name, 'wallet');
|
||||||
|
|
||||||
|
if (json.network)
|
||||||
|
assert.equal(json.network, network.type);
|
||||||
|
|
||||||
|
master = json.master;
|
||||||
|
json.master = json.accountKey;
|
||||||
|
wallet = new Wallet(json);
|
||||||
|
|
||||||
|
if (options.txs != null) {
|
||||||
|
options.txs.forEach(function(tx) {
|
||||||
|
wallet.syncOutputDepth(tx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.receiveDepth != null)
|
||||||
|
wallet.setReceiveDepth(options.receiveDepth);
|
||||||
|
|
||||||
|
if (options.changeDepth != null)
|
||||||
|
wallet.setChangeDepth(options.changeDepth);
|
||||||
|
|
||||||
|
wallet = wallet.toJSON();
|
||||||
|
wallet.master = master;
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
};
|
||||||
|
|
||||||
Wallet.fromJSON = function fromJSON(json, passphrase) {
|
Wallet.fromJSON = function fromJSON(json, passphrase) {
|
||||||
return new Wallet(Wallet._fromJSON(json, passphrase));
|
return new Wallet(Wallet._fromJSON(json, passphrase));
|
||||||
};
|
};
|
||||||
|
|||||||
@ -83,7 +83,7 @@ WalletDB.prototype.dump = function dump(callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
records[key] = value.slice(0, 50).toString('hex');
|
records[key] = value;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@ -116,8 +116,75 @@ WalletDB.prototype._init = function _init() {
|
|||||||
|
|
||||||
this.db = WalletDB._db[this.file];
|
this.db = WalletDB._db[this.file];
|
||||||
|
|
||||||
this.tx = new bcoin.txdb('w', this.db);
|
this.tx = new bcoin.txdb('w', this.db, {
|
||||||
this.tx._hasAddress = this.hasAddress.bind(this);
|
addressFilter: this.hasAddress.bind(this)
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tx.on('error', function(err) {
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tx.on('updated', function(tx) {
|
||||||
|
self.getIDs(tx.getOutputAddresses(), function(err, ids) {
|
||||||
|
if (err)
|
||||||
|
return self.emit('error', err);
|
||||||
|
|
||||||
|
self.emit('wallet tx', tx, ids);
|
||||||
|
|
||||||
|
// Only sync for confirmed txs.
|
||||||
|
if (tx.ts === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
utils.forEachSerial(ids, function(id, next) {
|
||||||
|
self.getJSON(id, function(err, json) {
|
||||||
|
if (err) {
|
||||||
|
self.emit('error', err);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate new addresses if necessary.
|
||||||
|
json = bcoin.wallet.sync(json, { txs: [tx] });
|
||||||
|
|
||||||
|
self.saveJSON(id, json, function(err) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
if (err)
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletDB.prototype.syncDepth = function syncDepth(id, changeDepth, receiveDepth, callback) {
|
||||||
|
callback = utils.ensure(callback);
|
||||||
|
|
||||||
|
if (!receiveDepth)
|
||||||
|
receiveDepth = 0;
|
||||||
|
|
||||||
|
if (!changeDepth)
|
||||||
|
changeDepth = 0;
|
||||||
|
|
||||||
|
self.getJSON(id, function(err, json) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
// Allocate new addresses if necessary.
|
||||||
|
json = bcoin.wallet.sync(json, {
|
||||||
|
receiveDepth: receiveDepth,
|
||||||
|
changeDepth: changeDepth
|
||||||
|
});
|
||||||
|
|
||||||
|
self.saveJSON(id, json, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
self.emit('sync depth', id, receiveDepth, changeDepth);
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
WalletDB.prototype.getJSON = function getJSON(id, callback) {
|
WalletDB.prototype.getJSON = function getJSON(id, callback) {
|
||||||
@ -251,6 +318,8 @@ WalletDB.prototype._removeDB = function _removeDB(id, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
WalletDB.prototype.get = function get(id, passphrase, callback) {
|
WalletDB.prototype.get = function get(id, passphrase, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
if (typeof passphrase === 'function') {
|
if (typeof passphrase === 'function') {
|
||||||
callback = passphrase;
|
callback = passphrase;
|
||||||
passphrase = null;
|
passphrase = null;
|
||||||
@ -268,14 +337,15 @@ WalletDB.prototype.get = function get(id, passphrase, callback) {
|
|||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
wallet = bcoin.wallet.fromJSON(options, passphrase);
|
options = bcoin.wallet._fromJSON(options, passphrase);
|
||||||
wallet.provider = self;
|
options.db = self;
|
||||||
|
options.tx = self.tx;
|
||||||
|
options.provider = self;
|
||||||
|
wallet = new bcoin.wallet(options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return callback(e);
|
return callback(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
wallet.on('add address', self._onAddress(wallet, wallet.id));
|
|
||||||
|
|
||||||
return callback(null, wallet);
|
return callback(null, wallet);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -286,14 +356,8 @@ WalletDB.prototype.save = function save(options, callback) {
|
|||||||
|
|
||||||
callback = utils.ensure(callback);
|
callback = utils.ensure(callback);
|
||||||
|
|
||||||
if (options instanceof bcoin.wallet) {
|
if (options instanceof bcoin.wallet)
|
||||||
if (!options.provider) {
|
assert(options.db === this);
|
||||||
options.on('add address', self._onAddress(options, options.id));
|
|
||||||
options.provider = self;
|
|
||||||
}
|
|
||||||
if (options instanceof bcoin.wallet)
|
|
||||||
options = options.toJSON();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveJSON(options.id, options, callback);
|
this.saveJSON(options.id, options, callback);
|
||||||
};
|
};
|
||||||
@ -304,7 +368,7 @@ WalletDB.prototype.remove = function remove(id, callback) {
|
|||||||
callback = utils.ensure(callback);
|
callback = utils.ensure(callback);
|
||||||
|
|
||||||
if (id instanceof bcoin.wallet) {
|
if (id instanceof bcoin.wallet) {
|
||||||
id.provider = null;
|
id.destroy();
|
||||||
id = id.id;
|
id = id.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,13 +401,18 @@ WalletDB.prototype.create = function create(options, callback) {
|
|||||||
|
|
||||||
if (json) {
|
if (json) {
|
||||||
try {
|
try {
|
||||||
wallet = bcoin.wallet.fromJSON(json, options.passphrase);
|
options = bcoin.wallet._fromJSON(json, options.passphrase);
|
||||||
wallet.provider = self;
|
options.db = self;
|
||||||
|
options.tx = self.tx;
|
||||||
|
options.provider = self;
|
||||||
|
wallet = new bcoin.wallet(options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return callback(e);
|
return callback(e);
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
} else {
|
} else {
|
||||||
|
options.db = self;
|
||||||
|
options.tx = self.tx;
|
||||||
options.provider = self;
|
options.provider = self;
|
||||||
wallet = new bcoin.wallet(options);
|
wallet = new bcoin.wallet(options);
|
||||||
self.saveJSON(wallet.id, wallet.toJSON(), done);
|
self.saveJSON(wallet.id, wallet.toJSON(), done);
|
||||||
@ -353,44 +422,46 @@ WalletDB.prototype.create = function create(options, callback) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
wallet.on('add address', self._onAddress(wallet, wallet.id));
|
|
||||||
|
|
||||||
return callback(null, wallet);
|
return callback(null, wallet);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
WalletDB.prototype._onAddress = function _onAddress(wallet, id) {
|
WalletDB.prototype.update = function update(wallet, address) {
|
||||||
var self = this;
|
var self = this;
|
||||||
return function(address) {
|
var batch;
|
||||||
var batch = self.db.batch();
|
|
||||||
|
|
||||||
|
// Ugly hack to avoid extra writes.
|
||||||
|
if (!wallet.changeAddress && wallet.changeDepth > 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
batch = this.db.batch();
|
||||||
|
|
||||||
|
batch.put(
|
||||||
|
'w/a/' + address.getKeyAddress() + '/' + wallet.id,
|
||||||
|
new Buffer([]));
|
||||||
|
|
||||||
|
if (address.type === 'multisig') {
|
||||||
batch.put(
|
batch.put(
|
||||||
'w/a/' + address.getKeyAddress() + '/' + id,
|
'w/a/' + address.getScriptAddress() + '/' + wallet.id,
|
||||||
new Buffer([]));
|
new Buffer([]));
|
||||||
|
}
|
||||||
|
|
||||||
if (address.type === 'multisig') {
|
if (address.witness) {
|
||||||
batch.put(
|
batch.put(
|
||||||
'w/a/' + address.getScriptAddress() + '/' + id,
|
'w/a/' + address.getProgramAddress() + '/' + wallet.id,
|
||||||
new Buffer([]));
|
new Buffer([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address.witness) {
|
batch.write(function(err) {
|
||||||
batch.put(
|
if (err)
|
||||||
'w/a/' + address.getProgramAddress() + '/' + id,
|
self.emit('error', err);
|
||||||
new Buffer([]));
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.write(function(err) {
|
self._saveDB(wallet.id, wallet.toJSON(), function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
self.emit('error', err);
|
self.emit('error', err);
|
||||||
|
|
||||||
self._saveDB(wallet.id, wallet.toJSON(), function(err) {
|
|
||||||
if (err)
|
|
||||||
self.emit('error', err);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
WalletDB.prototype.addTX = function addTX(tx, callback) {
|
WalletDB.prototype.addTX = function addTX(tx, callback) {
|
||||||
@ -448,7 +519,8 @@ WalletDB.prototype.getLast = function getLast(id, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
WalletDB.prototype.getAddresses = function getAddresses(id, callback) {
|
WalletDB.prototype.getAddresses = function getAddresses(id, callback) {
|
||||||
if (typeof id === 'string')
|
// Try to avoid a database lookup if we can...
|
||||||
|
if (typeof id === 'string' && bcoin.address.validate(id))
|
||||||
return callback(null, [id]);
|
return callback(null, [id]);
|
||||||
|
|
||||||
if (Array.isArray(id))
|
if (Array.isArray(id))
|
||||||
@ -476,44 +548,6 @@ WalletDB.prototype.getAddresses = function getAddresses(id, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
WalletDB.prototype.getIDs = function _getIDs(address, callback) {
|
|
||||||
var self = this;
|
|
||||||
var ids = [];
|
|
||||||
|
|
||||||
var iter = this.db.db.iterator({
|
|
||||||
gte: 'w/a/' + address,
|
|
||||||
lte: 'w/a/' + address + '~',
|
|
||||||
keys: true,
|
|
||||||
values: false,
|
|
||||||
fillCache: false,
|
|
||||||
keyAsBuffer: false
|
|
||||||
});
|
|
||||||
|
|
||||||
callback = utils.ensure(callback);
|
|
||||||
|
|
||||||
(function next() {
|
|
||||||
iter.next(function(err, key, value) {
|
|
||||||
if (err) {
|
|
||||||
return iter.end(function() {
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === undefined) {
|
|
||||||
return iter.end(function(err) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
return callback(null, ids);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ids.push(key.split('/')[2]);
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
WalletDB.prototype.hasAddress = function hasAddress(address, callback) {
|
WalletDB.prototype.hasAddress = function hasAddress(address, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@ -556,6 +590,64 @@ WalletDB.prototype.hasAddress = function hasAddress(address, callback) {
|
|||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WalletDB.prototype.getIDs = function getIDs(address, callback) {
|
||||||
|
var self = this;
|
||||||
|
var ids = [];
|
||||||
|
|
||||||
|
if (Array.isArray(address)) {
|
||||||
|
return utils.forEachSerial(address, function(address, next) {
|
||||||
|
self.getIDs(address, function(err, id) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
ids = ids.concat(id);
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
ids = utils.uniqs(ids);
|
||||||
|
|
||||||
|
return callback(null, ids);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var iter = this.db.db.iterator({
|
||||||
|
gte: 'w/a/' + address,
|
||||||
|
lte: 'w/a/' + address + '~',
|
||||||
|
keys: true,
|
||||||
|
values: false,
|
||||||
|
fillCache: false,
|
||||||
|
keyAsBuffer: false
|
||||||
|
});
|
||||||
|
|
||||||
|
callback = utils.ensure(callback);
|
||||||
|
|
||||||
|
(function next() {
|
||||||
|
iter.next(function(err, key, value) {
|
||||||
|
if (err) {
|
||||||
|
return iter.end(function() {
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === undefined) {
|
||||||
|
return iter.end(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
return callback(null, ids);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.push(key.split('/')[3]);
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user