walletdb improvements. start refactoring node object.

This commit is contained in:
Christopher Jeffrey 2016-02-29 21:33:11 -08:00
parent c41ded7360
commit 00b8f8950b
15 changed files with 699 additions and 218 deletions

View File

@ -4,10 +4,16 @@ var bcoin = require('bcoin');
var utils = bcoin.utils;
var assert = utils.assert;
var node = bcoin.node({
var node = bcoin.fullnode({
debug: true
});
node.on('error', function(err) {
utils.print(err.message);
});
node.pool.on('full', function() {
setInterval(function() {
console.log(node.wallet.getAll());
}, 1000);
});

View File

@ -76,8 +76,9 @@ bcoin.merkleblock = require('./bcoin/merkleblock');
bcoin.headers = require('./bcoin/headers');
bcoin.ramdisk = require('./bcoin/ramdisk');
bcoin.blockdb = require('./bcoin/blockdb');
bcoin.node = require('./bcoin/node2');
bcoin.spvnode = require('./bcoin/spvnode');
bcoin.node = require('./bcoin/node');
bcoin.fullnode = require('./bcoin/node');
bcoin.chainblock = require('./bcoin/chainblock');
bcoin.chaindb = require('./bcoin/chaindb');
bcoin.chain = require('./bcoin/chain');

View File

@ -15,12 +15,17 @@ var fs = bcoin.fs;
* BlockDB
*/
function BlockDB(options) {
function BlockDB(node, options) {
var self = this;
var levelup;
if (!(this instanceof BlockDB))
return new BlockDB(options);
return new BlockDB(node, options);
if (!(node instanceof bcoin.node)) {
options = node;
node = null;
}
// Some lazy loading
levelup = require('levelup');
@ -39,6 +44,7 @@ function BlockDB(options) {
this.options = options;
this.node = node;
this.data = new BlockData(options);
this.cache = {
@ -1023,10 +1029,10 @@ BlockDB.prototype.resetHeight = function resetHeight(height, callback, emit) {
};
BlockDB.prototype._getEntry = function _getEntry(height, callback) {
if (!bcoin.chain.global)
if (!this.node)
return callback();
return bcoin.chain.global.db.getAsync(height, callback);
return this.node.chain.db.getAsync(height, callback);
};
/**

View File

@ -18,9 +18,9 @@ var fs = bcoin.fs;
* Chain
*/
function Chain(options) {
function Chain(node, options) {
if (!(this instanceof Chain))
return new Chain(options);
return new Chain(node, options);
EventEmitter.call(this);
@ -35,8 +35,9 @@ function Chain(options) {
this.db = new bcoin.chaindb(this);
this.request = new utils.RequestCache();
this.loading = false;
this.mempool = options.mempool;
this.blockdb = options.blockdb;
this.node = node;
this.mempool = node.mempool;
this.blockdb = node.blockdb;
this.busy = false;
this.jobs = [];
this.pending = [];

View File

@ -69,8 +69,10 @@ HTTPServer.prototype._init = function _init() {
self.node.getCoinByAddress(addresses, function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
@ -81,8 +83,10 @@ HTTPServer.prototype._init = function _init() {
self.node.getCoin(req.params.hash, +req.params.index, function(err, coin) {
if (err)
return next(err);
if (!coin)
return send(404);
send(200, coin.toJSON());
});
});
@ -92,8 +96,10 @@ HTTPServer.prototype._init = function _init() {
self.node.getCoinByAddress(req.body.addresses, function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
@ -104,8 +110,10 @@ HTTPServer.prototype._init = function _init() {
self.node.getTX(req.params.hash, function(err, tx) {
if (err)
return next(err);
if (!tx)
return send(404);
send(200, tx.toJSON());
});
});
@ -116,8 +124,10 @@ HTTPServer.prototype._init = function _init() {
self.node.getTXByAddress(addresses, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
@ -145,8 +155,10 @@ HTTPServer.prototype._init = function _init() {
self.node.getBlock(hash, function(err, block) {
if (err)
return next(err);
if (!block)
return send(404);
send(200, block.toJSON());
});
});
@ -156,8 +168,10 @@ HTTPServer.prototype._init = function _init() {
self.node.walletdb.getJSON(req.params.id, function(err, json) {
if (err)
return next(err);
if (!json)
return send(404);
send(200, json);
});
});
@ -165,33 +179,71 @@ HTTPServer.prototype._init = function _init() {
// Create/get wallet
this.post('/wallet/:id', function(req, res, next, send) {
req.body.id = req.params.id;
self.node.walletdb.create(req.body, function(err, json) {
self.node.walletdb.createJSON(req.body.id, req.body, function(err, json) {
if (err)
return next(err);
if (!json)
return send(404);
send(json);
send(200, json);
});
});
// Update wallet / sync address depth
this.put('/wallet/:id', function(req, res, next, send) {
req.body.id = req.params.id;
self.node.walletdb.save(req.body.id, req.body, function(err, json) {
self.node.walletdb.saveJSON(req.body.id, req.body, function(err, json) {
if (err)
return next(err);
if (!json)
return send(404);
send(json);
send(200, json);
});
});
// Wallet UTXOs
this.get('/wallet/:id/utxo', function(req, res, next) {
this.get('/wallet/:id/utxo', function(req, res, next, send) {
self.node.walletdb.getJSON(req.params.id, function(err, json) {
if (err)
return next(err);
if (!json)
return send(404);
self.node.getCoinByAddress(Object.keys(json.addressMap), function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
});
// Wallet TXs
this.get('/wallet/:id/tx', function(req, res, next) {
this.get('/wallet/:id/tx', function(req, res, next, send) {
self.node.walletdb.getJSON(req.params.id, function(err, json) {
if (err)
return next(err);
if (!json)
return send(404);
self.node.getTXByAddress(Object.keys(json.addressMap), function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, coins.map(function(tx) { return tx.toJSON(); }));
});
});
});
};

View File

@ -20,7 +20,7 @@ var fs = bcoin.fs;
function Mempool(node, options) {
if (!(this instanceof Mempool))
return new Mempool(pool, options);
return new Mempool(node, options);
if (!options)
options = {};

View File

@ -16,9 +16,9 @@ var EventEmitter = require('events').EventEmitter;
* Miner
*/
function Miner(pool, options) {
function Miner(node, options) {
if (!(this instanceof Miner))
return new Miner(options);
return new Miner(node, options);
EventEmitter.call(this);
@ -27,19 +27,19 @@ function Miner(pool, options) {
this.options = options;
this.address = this.options.address;
this.msg = this.options.msg || 'mined by bcoin';
this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin';
this.pool = pool || bcoin.pool.global;
this.chain = this.pool.chain;
this.mempool = this.pool.mempool;
this.blockdb = this.pool.blockdb;
this.node = node;
this.pool = node.pool;
this.chain = node.chain;
this.mempool = node.mempool;
this.running = false;
this.timeout = null;
this.interval = null;
this.fee = new bn(0);
this.last = this.chain.tip;
this.last = this.node.chain.tip;
this.block = null;
this.iterations = 0;
this._begin = utils.now();
@ -229,7 +229,7 @@ Miner.prototype.createBlock = function createBlock(tx) {
new Buffer(utils.nonce().toArray()),
// Let the world know this little
// miner succeeded.
new Buffer(this.msg || 'mined by bcoin', 'ascii')
new Buffer(this.coinbaseFlags || 'mined by bcoin', 'ascii')
],
sequence: 0xffffffff
});

View File

@ -699,6 +699,9 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
if (bcoin.script.isWitnessProgram(prev)) {
witness = true;
// Now calculating vsize. The regular
// redeem script (if there was one)
// is now worth 4 points.
size *= 4;
if (this.inputs[i].witness.length && bcoin.script.isWitnessScripthash(prev)) {
prev = this.inputs[i].witness[this.inputs[i].witness.length - 1];
@ -763,9 +766,10 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
}
}
// Byte for varint size of input script
// Byte for varint size of input script.
size += utils.sizeIntv(size);
// Calculate vsize if we're a witness program.
if (witness)
size = (size + 3) / 4 | 0;

View File

@ -1,5 +1,5 @@
/**
* node.js - full node for bcoin
* fullnode.js - full node for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
@ -14,68 +14,66 @@ var assert = utils.assert;
var fs = bcoin.fs;
/**
* Node
* Fullnode
*/
function Node(options) {
if (!(this instanceof Node))
return new Node(options);
function Fullnode(options) {
if (!(this instanceof Fullnode))
return new Fullnode(options);
EventEmitter.call(this);
if (!options)
options = {};
this.options = options;
bcoin.node.call(this, options);
if (this.options.debug)
bcoin.debug = this.options.debug;
this.options.http = {};
if (this.options.network)
network.set(this.options.network);
if (!this.options.wallet)
this.options.wallet = {};
this.blockdb = null;
this.mempool = null;
this.pool = null;
this.chain = null;
this.miner = null;
this.wallet = null;
if (!this.options.wallet.id)
this.options.wallet.id = 'primary';
if (!this.options.wallet.passphrase)
this.options.wallet.passphrase = 'node';
this.loading = false;
Node.global = this;
Fullnode.global = this;
this._init();
}
utils.inherits(Node, EventEmitter);
utils.inherits(Fullnode, bcoin.node);
Node.prototype._init = function _init() {
Fullnode.prototype._init = function _init() {
var self = this;
this.loading = true;
if (!this.options.pool)
this.options.pool = {};
// BlockDB and Mempool need to be instantiated
// first because the chain needs access to them.
this.blockdb = new bcoin.blockdb(this, {
cache: false
});
if (!this.options.miner)
this.options.miner = {};
this.mempool = new bcoin.mempool(this);
this.blockdb = new bcoin.blockdb(this.options.blockdb);
this.mempool = new bcoin.mempool(this, this.options.mempool);
// Chain is instantiated next. The pool needs it.
this.chain = new bcoin.chain(this, {
preload: false
});
this.options.pool.spv = false;
this.options.pool.blockdb = this.blockdb;
this.options.pool.mempool = this.mempool;
this.pool = new bcoin.pool(this, {
witness: this.network.type === 'segnet',
spv: false
});
this.pool = new bcoin.pool(this.options.pool);
this.chain = this.pool.chain;
this.miner = new bcoin.miner(this, this.options.miner);
this.walletdb = new bcoin.walletdb(this, this.options.walletdb);
this.miner = new bcoin.miner(this.pool, this.options.miner);
this.walletdb = new bcoin.walletdb(this.options.walletdb);
this.options.http = {};
if (this.options.http && bcoin.http) {
this.http = new bcoin.http(this, this.options.http);
this.http.listen(this.options.http.port || 8080);
@ -89,61 +87,79 @@ Node.prototype._init = function _init() {
self.emit('error', err);
});
if (!this.options.wallet)
this.options.wallet = {};
this.on('tx', function(tx) {
self.walletdb.ownTX(tx, function(err, input, output) {
if (err)
return self.emit('error', err);
if (!this.options.wallet.id)
this.options.wallet.id = 'primary';
self.emit('own tx', tx, input, output);
});
});
if (!this.options.wallet.passphrase)
this.options.wallet.passphrase = 'node';
this.chain.on('block', function(block) {
self.emit('block', block);
block.txs.forEach(function(tx) {
self.emit('tx', tx, block);
});
});
this.walletdb.create(this.options.wallet, function(err, wallet) {
this.mempool.on('tx', function(tx) {
self.emit('tx', tx);
});
// Handle forks
this.chain.on('remove entry', function(entry) {
self.wallets.forEach(function(wallet) {
wallet.tx.getAll().forEach(function(tx) {
if (tx.block === entry.hash || tx.height >= entry.height)
wallet.tx.unconfirm(tx);
});
});
});
this.createWallet(this.options.wallet, function(err, wallet) {
if (err)
throw err;
self.wallet = wallet;
self.miner.address = wallet.getAddress();
self.pool.startSync();
self.loading = false;
self.emit('load');
});
};
Fullnode.prototype.createWallet = function createWallet(options, callback) {
var self = this;
callback = utils.ensure(callback);
this.walletdb.create(options, function(err, wallet) {
if (err)
return callback(err);
assert(wallet);
utils.debug('Loaded wallet with id=%s address=%s',
wallet.getID(), wallet.getAddress());
self.chain.on('block', function(block) {
block.txs.forEach(function(tx) {
self.wallet.addTX(tx);
});
});
self.wallets.push(wallet);
self.mempool.on('tx', function(tx) {
self.wallet.addTX(tx);
});
return callback(null, wallet);
self.miner.address = self.wallet.getAddress();
// Handle forks
self.chain.on('remove entry', function(entry) {
self.wallet.tx.getAll().forEach(function(tx) {
if (tx.block === entry.hash || tx.height >= entry.height)
self.wallet.tx.unconfirm(tx);
});
});
self.pool.addWallet(self.wallet, function(err) {
self.pool.addWallet(wallet, function(err) {
if (err)
throw err;
return callback(err);
self.pool.startSync();
self.loading = false;
self.emit('load');
return callback(null, wallet);
});
});
};
Node.prototype.scanWallet = function scanWallet(callback) {
this.wallet.scan(this.getTXByAddress.bind(this), callback);
Fullnode.prototype.scanWallet = function scanWallet(wallet, callback) {
wallet.scan(this.getTXByAddress.bind(this), callback);
};
Node.prototype.getBlock = function getBlock(hash, callback) {
Fullnode.prototype.getBlock = function getBlock(hash, callback) {
var self = this;
var coin;
@ -158,7 +174,7 @@ Node.prototype.getBlock = function getBlock(hash, callback) {
});
};
Node.prototype.getCoin = function getCoin(hash, index, callback) {
Fullnode.prototype.getCoin = function getCoin(hash, index, callback) {
var self = this;
var coin;
@ -182,7 +198,7 @@ Node.prototype.getCoin = function getCoin(hash, index, callback) {
});
};
Node.prototype.getCoinByAddress = function getCoinByAddress(addresses, callback) {
Fullnode.prototype.getCoinByAddress = function getCoinByAddress(addresses, callback) {
var self = this;
var mempool;
@ -202,7 +218,7 @@ Node.prototype.getCoinByAddress = function getCoinByAddress(addresses, callback)
});
};
Node.prototype.getTX = function getTX(hash, callback) {
Fullnode.prototype.getTX = function getTX(hash, callback) {
var self = this;
var tx;
@ -223,7 +239,7 @@ Node.prototype.getTX = function getTX(hash, callback) {
});
};
Node.prototype.isSpent = function isSpent(hash, index, callback) {
Fullnode.prototype.isSpent = function isSpent(hash, index, callback) {
var self = this;
callback = utils.asyncify(callback);
@ -234,7 +250,7 @@ Node.prototype.isSpent = function isSpent(hash, index, callback) {
this.blockdb.isSpent(hash, index, callback);
};
Node.prototype.getTXByAddress = function getTXByAddress(addresses, callback) {
Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses, callback) {
var self = this;
var mempool;
@ -250,7 +266,7 @@ Node.prototype.getTXByAddress = function getTXByAddress(addresses, callback) {
});
};
Node.prototype.fillCoin = function fillCoin(tx, callback) {
Fullnode.prototype.fillCoin = function fillCoin(tx, callback) {
callback = utils.asyncify(callback);
if (this.mempool.fillCoin(tx))
@ -259,7 +275,7 @@ Node.prototype.fillCoin = function fillCoin(tx, callback) {
this.blockdb.fillCoin(tx, callback);
};
Node.prototype.fillTX = function fillTX(tx, callback) {
Fullnode.prototype.fillTX = function fillTX(tx, callback) {
callback = utils.asyncify(callback);
if (this.mempool.fillTX(tx))
@ -272,4 +288,4 @@ Node.prototype.fillTX = function fillTX(tx, callback) {
* Expose
*/
module.exports = Node;
module.exports = Fullnode;

54
lib/bcoin/node2.js Normal file
View File

@ -0,0 +1,54 @@
/**
* node.js - node object for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var EventEmitter = require('events').EventEmitter;
var bcoin = require('../bcoin');
var bn = require('bn.js');
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var utils = bcoin.utils;
var assert = utils.assert;
var fs = bcoin.fs;
/**
* Node
*/
function Node(options) {
if (!(this instanceof Node))
return new Node(options);
EventEmitter.call(this);
if (!options)
options = {};
this.options = options;
if (this.options.debug)
bcoin.debug = this.options.debug;
if (this.options.network)
network.set(this.options.network);
this.network = network;
this.blockdb = null;
this.mempool = null;
this.pool = null;
this.chain = null;
this.miner = null;
this.wallets = [];
Node.global = this;
}
utils.inherits(Node, EventEmitter);
/**
* Expose
*/
module.exports = Node;

View File

@ -16,12 +16,12 @@ var constants = bcoin.protocol.constants;
* Pool
*/
function Pool(options) {
function Pool(node, options) {
var self = this;
var Chain;
if (!(this instanceof Pool))
return new Pool(options);
return new Pool(node, options);
EventEmitter.call(this);
@ -55,8 +55,9 @@ function Pool(options) {
this.destroyed = false;
this.size = options.size || 8;
this.blockdb = options.blockdb;
this.mempool = options.mempool;
this.chain = node.chain;
this.blockdb = node.blockdb;
this.mempool = node.mempool;
if (options.spv) {
if (options.headers == null)
@ -76,12 +77,12 @@ function Pool(options) {
this.requestTimeout = options.requestTimeout || 2 * 60000;
this.chain = new bcoin.chain({
spv: options.spv,
preload: options.preload,
blockdb: options.blockdb,
mempool: options.mempool
});
// this.chain = new bcoin.chain({
// spv: options.spv,
// preload: options.preload,
// blockdb: options.blockdb,
// mempool: options.mempool
// });
this.watchMap = {};
@ -118,7 +119,7 @@ function Pool(options) {
type: 'tx'
};
if (network.type === 'segnet') {
if (this.options.witness) {
this.block.type = 'witness' + this.block.type;
this.tx.type = 'witness' + this.tx.type;
}

View File

@ -465,6 +465,57 @@ TX.prototype.getFunds = function getFunds(side) {
return this.getOutputValue();
};
TX.prototype.getInputAddresses = function getInputAddresses() {
var table = {};
var addresses = [];
var i, address;
for (i = 0; i < this.inputs.length; i++) {
address = this.inputs[i].getAddress();
if (address && !table[address]) {
table[address] = true;
addresses.push(address);
}
}
addresses.table = table;
return addresses;
};
TX.prototype.getOutputAddresses = function getOutputAddresses() {
var table = {};
var addresses = [];
var i, address;
for (i = 0; i < this.outputs.length; i++) {
address = this.outputs[i].getAddress();
if (address && !table[address]) {
table[address] = true;
addresses.push(address);
}
}
addresses.table = table;
return addresses;
};
TX.prototype.getAddresses = function getAddresses() {
var input = this.getInputAddresses();
var output = this.getOutputAddresses();
var i;
for (i = 0; i < output.length; i++) {
if (!input.table[output[i]]) {
input.table[output[i]] = true;
input.push(output[i]);
}
}
return input;
};
TX.prototype.testInputs = function testInputs(addressTable, index) {
var i, input;

View File

@ -415,6 +415,14 @@ utils.asyncify = function asyncify(callback) {
return asyncifyFn;
};
utils.nop = function() {};
utils.ensure = function ensure(callback) {
if (!callback)
return utils.nop;
return callback;
};
utils.revHex = function revHex(s) {
var r = '';
var i = 0;

View File

@ -373,12 +373,17 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
address = new bcoin.address(options);
this.addressMap[address.getKeyAddress()] = data.path;
this._saveAddress(address.getKeyAddress());
if (this.type === 'multisig')
if (this.type === 'multisig') {
this.addressMap[address.getScriptAddress()] = data.path;
this._saveAddress(address.getScriptAddress());
}
if (this.witness)
if (this.witness) {
this.addressMap[address.getProgramAddress()] = data.path;
this._saveAddress(address.getProgramAddress());
}
this.emit('add address', address);
@ -876,7 +881,7 @@ Wallet.prototype.__defineGetter__('address', function() {
return this.getAddress();
});
Wallet.prototype.toJSON = function toJSON(noPool) {
Wallet.prototype.toJSON = function toJSON() {
return {
v: 3,
name: 'wallet',
@ -897,7 +902,7 @@ Wallet.prototype.toJSON = function toJSON(noPool) {
return key.xpubkey;
}),
balance: utils.btc(this.getBalance()),
txs: noPool ? [] : this.tx.getAll().map(function(tx) {
txs: this.options.noPool ? [] : this.tx.getAll().map(function(tx) {
return tx.toCompact();
})
};
@ -926,6 +931,7 @@ Wallet._fromJSON = function _fromJSON(json, passphrase) {
master: bcoin.hd.fromJSON(json.master, passphrase),
addressMap: json.addressMap,
keys: json.keys,
passphrase: passphrase,
txs: json.txs.map(function(json) {
return bcoin.tx.fromCompact(json);
})
@ -936,11 +942,20 @@ Wallet.fromJSON = function fromJSON(json, passphrase) {
return new Wallet(Wallet._fromJSON(json, passphrase));
};
Wallet.prototype.save = function save(callback) {
callback = utils.asyncify(callback);
Wallet.prototype._saveAddress = function _saveAddress(address, callback) {
callback = utils.ensure(callback);
if (!this.options.store && !this.options.db)
return callback();
if (!this.options.store || !this.options.db)
return utils.nextTick(callback);
return this.db.saveAddress(this.id, address, callback);
};
Wallet.prototype.save = function save(callback) {
callback = utils.ensure(callback);
if (!this.options.store || !this.options.db)
return utils.nextTick(callback);
return this.db.save(this.id, this, callback);
};

View File

@ -19,20 +19,25 @@ var fs = bcoin.fs;
* WalletDB
*/
function WalletDB(options) {
function WalletDB(node, options) {
if (!(this instanceof WalletDB))
return new WalletDB(options);
return new WalletDB(node, options);
if (WalletDB.global)
return WalletDB.global;
if (!(node instanceof bcoin.node)) {
options = node;
node = null;
}
if (!options)
options = {};
EventEmitter.call(this);
this.node = node;
this.options = options;
this.file = options.file;
this.dir = options.dir;
this.type = options.type;
@ -112,107 +117,128 @@ WalletDB.prototype._init = function _init() {
throw new Error('Unknown storage type: ' + this.type);
};
WalletDB.prototype.save = function save(id, json, callback) {
callback = utils.asyncify(callback);
WalletDB.prototype.getJSON = function getJSON(id, callback) {
if (typeof id === 'object')
id = id.id;
callback = utils.ensure(callback);
if (this.type === 'leveldb')
return this.saveDB(id, json, callback);
return this._getDB(id, callback);
if (this.type === 'file')
return this.saveFile(id, json, callback);
return this._getFile(id, callback);
throw new Error('Unknown storage type: ' + this.type);
};
WalletDB.prototype.saveDB = function saveFile(id, json, callback) {
var key;
WalletDB.prototype.saveJSON = function saveJSON(id, json, callback) {
var self = this;
key = 'w/' + id;
callback = utils.ensure(callback);
if (json instanceof bcoin.wallet) {
json.store = true;
json.db = this;
json = json.toJSON(this.options.noPool);
json = json.toJSON();
} else {
if (typeof json.v !== 'number') {
json = utils.merge({}, json);
delete json.store;
delete json.db;
json = new bcoin.wallet(json).toJSON();
}
}
callback = utils.asyncify(callback);
function cb(err, json) {
var batch;
json = JSON.stringify(json);
this.db.put(key, json, callback);
};
WalletDB.prototype.saveFile = function saveFile(id, json, callback) {
var file, options;
file = this.dir + '/' + id + '.json';
if (json instanceof bcoin.wallet) {
json.store = true;
json.db = this;
json = json.toJSON(this.options.noPool);
}
callback = utils.asyncify(callback);
if (!bcoin.fs)
return callback();
json = JSON.stringify(json, null, 2);
options = {
encoding: 'utf8',
mode: 0600
};
fs.writeFile(file, json, options, function(err) {
if (err)
return callback(err);
return callback(null, file);
});
};
if (json && self.type === 'leveldb') {
batch = self.db.batch();
Object.keys(json.addressMap).forEach(function(address) {
batch.put('a/' + address + '/' + json.id, '');
});
return batch.write(function(err) {
if (err)
return callback(err);
return callback(null, json);
});
}
WalletDB.prototype.getJSON = function getJSON(id, passphrase, callback) {
if (typeof passphrase === 'function') {
callback = passphrase;
passphrase = null;
}
callback = utils.asyncify(callback);
if (id instanceof bcoin.wallet) {
id = wallet.id;
json.store = true;
json.db = this;
return callback(null, json);
}
if (this.type === 'leveldb')
return this.getDB(id, passphrase, callback);
return this._saveDB(id, json, cb);
if (this.type === 'file')
return this.getFile(id, passphrase, callback);
return this._saveFile(id, json, cb);
throw new Error('Unknown storage type: ' + this.type);
};
WalletDB.prototype.getFile = function getFile(id, passphrase, callback) {
WalletDB.prototype.removeJSON = function removeJSON(id, callback) {
var self = this;
callback = utils.ensure(callback);
if (typeof id === 'object')
id = id.id;
function cb(err, json) {
var batch;
if (err)
return callback(err);
if (json && self.type === 'leveldb') {
batch = self.db.batch();
Object.keys(json.addressMap).forEach(function(address) {
batch.del('a/' + address + '/' + json.id);
});
return batch.write(function(err) {
if (err)
return callback(err);
return callback(null, json);
});
}
return callback(null, json);
}
if (this.type === 'leveldb')
return this._removeDB(id, cb);
if (this.type === 'file')
return this._removeFile(id, cb);
throw new Error('Unknown storage type: ' + this.type);
};
WalletDB.prototype.createJSON = function createJSON(id, options, callback) {
var self = this;
callback = utils.ensure(callback);
return this.getJSON(id, function(err, json) {
if (err)
return callback(err);
if (!json)
return self.saveJSON(options.id, options, callback);
return callback(null, json);
});
};
WalletDB.prototype._getFile = function _getFile(id, callback) {
var self = this;
var file;
callback = utils.asyncify(callback);
if (!bcoin.fs)
return callback();
if (!id)
return callback();
callback = utils.ensure(callback);
file = this.dir + '/' + id + '.json';
fs.readFile(file, 'utf8', function(err, json) {
var options;
if (err && err.code === 'ENOENT')
return callback();
@ -220,29 +246,24 @@ WalletDB.prototype.getFile = function getFile(id, passphrase, callback) {
return callback(err);
try {
options = bcoin.wallet._fromJSON(JSON.parse(json), passphrase);
json = JSON.parse(json);
} catch (e) {
return callback(e);
}
options.store = true;
options.db = self;
return callback(null, options);
return callback(null, json);
});
};
WalletDB.prototype.getDB = function getDB(id, passphrase, callback) {
WalletDB.prototype._getDB = function _getDB(id, callback) {
var self = this;
var key;
callback = utils.asyncify(callback);
callback = utils.ensure(callback);
key = 'w/' + id;
this.db.get(key, function(err, json) {
var options;
if (err && err.type === 'NotFoundError')
return callback();
@ -250,45 +271,288 @@ WalletDB.prototype.getDB = function getDB(id, passphrase, callback) {
return callback(err);
try {
options = bcoin.wallet._fromJSON(JSON.parse(json), passphrase);
json = JSON.parse(json);
} catch (e) {
return callback(e);
}
options.store = true;
options.db = self;
return callback(null, json);
});
};
return callback(null, options);
WalletDB.prototype._saveDB = function _saveDB(id, json, callback) {
var key = 'w/' + id;
var data;
callback = utils.ensure(callback);
data = JSON.stringify(json);
this.db.put(key, data, function(err) {
if (err)
return callback(err);
return callback(null, json);
});
};
WalletDB.prototype._saveFile = function _saveFile(id, json, callback) {
var file = this.dir + '/' + id + '.json';
var options, data;
callback = utils.ensure(callback);
data = JSON.stringify(json, null, 2);
options = {
encoding: 'utf8',
mode: 0600
};
fs.writeFile(file, data, options, function(err) {
if (err)
return callback(err);
return callback(null, json);
});
};
WalletDB.prototype._removeDB = function _removeDB(id, callback) {
var self = this;
var key = 'w/' + id;
callback = utils.ensure(callback);
this._getDB(id, function(err, json) {
if (err)
return callback(err);
self.db.del(key, function(err) {
if (err && err.type !== 'NotFoundError')
return callback(err);
return callback(null, json);
});
});
};
WalletDB.prototype._removeFile = function _removeFile(id, callback) {
var file = this.dir + '/' + id + '.json';
callback = utils.ensure(callback);
this._getFile(id, function(err, json) {
if (err)
return callback(err);
fs.unlink(file, function(err) {
if (err && err.code !== 'ENOENT')
return callback(err);
return callback(null, json);
});
});
};
WalletDB.prototype.get = function get(id, passphrase, callback) {
callback = utils.asyncify(callback);
return this.getJSON(id, passphrase, function(err, options) {
if (typeof passphrase === 'function') {
callback = passphrase;
passphrase = null;
}
callback = utils.ensure(callback);
return this.getJSON(id, function(err, options) {
var wallet;
if (err)
return callback(err);
if (!options)
return callback();
return callback(null, new bcoin.wallet(options));
wallet = bcoin.wallet.fromJSON(options, passphrase);
wallet.store = true;
wallet.db = self;
return callback(null, wallet);
});
};
WalletDB.prototype.save = function save(options, callback) {
var self = this;
var passphrase = options.passphrase;
callback = utils.ensure(callback);
return this.saveJSON(options.id, options, callback);
};
WalletDB.prototype.remove = function save(id, callback) {
var self = this;
callback = utils.ensure(callback);
if (id instanceof bcoin.wallet) {
id.store = false;
id.db = null;
id = id.id;
}
return this.removeJSON(id, callback);
};
WalletDB.prototype.create = function create(options, callback) {
var self = this;
callback = utils.asyncify(callback);
return this.getJSON(options.id, options.passphrase, function(err, opt) {
var passphrase = options.passphrase;
callback = utils.ensure(callback);
if (options instanceof bcoin.wallet) {
options.store = true;
options.db = this;
}
return this.createJSON(options.id, options, function(err, json) {
var wallet;
if (err)
return callback(err);
if (!opt) {
options.store = true;
options.db = self;
return callback(null, new bcoin.wallet(options));
}
wallet = bcoin.wallet.fromJSON(json, options.passphrase);
wallet.store = true;
wallet.db = self;
return callback(null, new bcoin.wallet(opt));
return callback(null, wallet);
});
};
WalletDB.prototype.saveAddress = function saveAddress(id, address, callback) {
callback = utils.ensure(callback);
if (this.type !== 'leveldb')
return utils.nextTick(callback);
this.db.put('a/' + address + '/' + id, '', callback);
};
WalletDB.prototype.removeAddress = function removeAddress(id, address, callback) {
callback = utils.ensure(callback);
if (this.type !== 'leveldb')
return utils.nextTick(callback);
this.db.del('a/' + address + '/' + id, callback);
};
WalletDB.prototype._getIDs = function _getIDs(address, callback) {
var self = this;
var ids = [];
var iter = this.db.db.iterator({
gte: 'a/' + address,
lte: '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.test = function test(addresses, callback) {
var self = this;
if (this.type !== 'leveldb')
return utils.nextTick(callback);
utils.forEachSerial(addresses, function(address, next) {
self._getIDs(address, function(err, ids) {
if (err)
return next(err);
if (ids.length > 0)
return callback(null, ids);
next();
});
}, function(err) {
if (err)
return callback(err);
return callback();
});
};
WalletDB.prototype.ownInput = function ownInput(tx, callback) {
var self = this;
var addresses;
if (tx.getAddress) {
assert(tx instanceof bcoin.input);
addresses = tx.getAddress();
if (addresses)
addresses = [addresses];
else
addresses = [];
} else {
addresses = tx.getInputAddresses();
}
return this.test(addresses, callback);
};
WalletDB.prototype.ownOutput = function ownOutput(tx, callback) {
var self = this;
var addresses;
if (tx.getAddress) {
assert(tx instanceof bcoin.output);
addresses = tx.getAddress();
if (addresses)
addresses = [addresses];
else
addresses = [];
} else {
addresses = tx.getOutputAddresses();
}
return this.test(addresses, callback);
};
WalletDB.prototype.ownTX = function ownTX(tx, callback) {
var self = this;
return this.ownInput(tx, function(err, input) {
if (err)
return callback(err);
return self.ownOutput(tx, function(err, output) {
if (err)
return callback(err);
if (input || output)
return callback(null, input || [], output || []);
return callback();
});
});
};
@ -296,4 +560,6 @@ WalletDB.prototype.create = function create(options, callback) {
* Expose
*/
var self = this;
var self = this;
module.exports = WalletDB;