From b11cdd80aff7eae73eaa3da9af2888c15bdbe9d5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 20 Feb 2016 17:54:51 -0800 Subject: [PATCH] add walletdb. --- lib/bcoin.js | 4 +- lib/bcoin/node.js | 7 +- lib/bcoin/spvnode.js | 12 +- lib/bcoin/tx-pool.js | 10 +- lib/bcoin/tx.js | 2 +- lib/bcoin/wallet.js | 125 ++---------------- lib/bcoin/walletdb.js | 289 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 321 insertions(+), 128 deletions(-) create mode 100644 lib/bcoin/walletdb.js diff --git a/lib/bcoin.js b/lib/bcoin.js index ebdc86cf..99737476 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -62,10 +62,11 @@ bcoin.input = require('./bcoin/input'); bcoin.output = require('./bcoin/output'); bcoin.coin = require('./bcoin/coin'); bcoin.tx = require('./bcoin/tx'); -bcoin.txPool = require('./bcoin/tx-pool'); +bcoin.txpool = require('./bcoin/tx-pool'); bcoin.block = require('./bcoin/block'); bcoin.ramdisk = require('./bcoin/ramdisk'); bcoin.blockdb = require('./bcoin/blockdb'); +bcoin.spvnode = require('./bcoin/spvnode'); bcoin.node = require('./bcoin/node'); bcoin.chainblock = require('./bcoin/chainblock'); bcoin.chaindb = require('./bcoin/chaindb'); @@ -73,6 +74,7 @@ bcoin.chain = require('./bcoin/chain'); bcoin.mempool = require('./bcoin/mempool'); bcoin.keypair = require('./bcoin/keypair'); bcoin.address = require('./bcoin/address'); +bcoin.walletdb = require('./bcoin/walletdb'); bcoin.wallet = require('./bcoin/wallet'); bcoin.peer = require('./bcoin/peer'); bcoin.pool = require('./bcoin/pool'); diff --git a/lib/bcoin/node.js b/lib/bcoin/node.js index d23ba68d..1c1ea831 100644 --- a/lib/bcoin/node.js +++ b/lib/bcoin/node.js @@ -74,6 +74,8 @@ Node.prototype._init = function _init() { this.miner = new bcoin.miner(this.pool, this.options.miner); + this.walletdb = new bcoin.walletdb(this.options.walletdb); + this.mempool.on('error', function(err) { self.emit('error', err); }); @@ -88,7 +90,10 @@ Node.prototype._init = function _init() { if (!this.options.wallet.id) this.options.wallet.id = 'primary'; - bcoin.wallet.load(this.options.wallet, function(err, wallet) { + if (!this.options.wallet.passphrase) + this.options.wallet.passphrase = 'node'; + + this.walletdb.create(this.options.wallet, function(err, wallet) { if (err) throw err; diff --git a/lib/bcoin/spvnode.js b/lib/bcoin/spvnode.js index 8a87c445..98c89170 100644 --- a/lib/bcoin/spvnode.js +++ b/lib/bcoin/spvnode.js @@ -62,6 +62,8 @@ SPVNode.prototype._init = function _init() { this.pool = new bcoin.pool(this.options.pool); this.chain = this.pool.chain; + this.walletdb = new bcoin.walletdb(this.options.walletdb); + this.pool.on('error', function(err) { self.emit('error', err); }); @@ -76,7 +78,10 @@ SPVNode.prototype._init = function _init() { if (!this.options.wallet.id) this.options.wallet.id = 'primary'; - bcoin.wallet.load(this.options.wallet, function(err, wallet) { + if (!this.options.wallet.passphrase) + this.options.wallet.passphrase = 'node'; + + this.walletdb.create(this.options.wallet, function(err, wallet) { if (err) throw err; @@ -93,11 +98,6 @@ SPVNode.prototype._init = function _init() { }); }); - self.pool.startSync(); - - self.loading = false; - self.emit('load'); - return; self.pool.addWallet(this.wallet, function(err) { if (err) throw err; diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js index 32bc88ae..c16731dd 100644 --- a/lib/bcoin/tx-pool.js +++ b/lib/bcoin/tx-pool.js @@ -212,7 +212,10 @@ TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) { if (noWrite) return; - this._wallet.saveFile(); + this._wallet.save(function(err) { + if (err) + self.emit('error', err); + }); }; TXPool.prototype._removeTX = function _removeTX(tx, noWrite) { @@ -233,7 +236,10 @@ TXPool.prototype._removeTX = function _removeTX(tx, noWrite) { if (noWrite) return; - this._wallet.saveFile(); + this._wallet.save(function(err) { + if (err) + self.emit('error', err); + }); }; TXPool.prototype.removeTX = function removeTX(hash) { diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 07dfbf64..6df733d6 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -1463,7 +1463,7 @@ TX.prototype.hasPrevout = function hasPrevout() { TX.prototype.fillPrevout = function fillPrevout(txs, unspent) { var inputs; - if (txs instanceof bcoin.txPool) { + if (txs instanceof bcoin.txpool) { unspent = txs._unspent; txs = txs._all; } else if (txs instanceof bcoin.wallet) { diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index cf0266cd..698a84c1 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -46,6 +46,7 @@ function Wallet(options) { options.master = bcoin.hd.privateKey(); this.options = options; + this.db = options.db || new bcoin.walletdb({ type: 'file' }); this.addresses = []; this.master = options.master || null; this.addressMap = options.addressMap || {}; @@ -133,14 +134,7 @@ Wallet.prototype._init = function _init() { assert(this.changeAddress.change); this.id = this.getID(); - this.file = options.file; - - if (!this.file || this.file === true) { - this.file = bcoin.dir + '/wallet-' - + this.id + '-' + network.type + '.json'; - } - - this.tx = new bcoin.txPool(this); + this.tx = new bcoin.txpool(this); // Notify owners about new accepted transactions this.tx.on('update', function(lastTs, lastHeight, tx) { @@ -175,9 +169,10 @@ Wallet.prototype._init = function _init() { this.lastTs = this.tx._lastTs; this.lastHeight = this.tx._lastHeight; - this.saveFile(); + this.save(function(err) { + if (err) + throw err; - utils.nextTick(function() { self.loading = false; self.emit('load', self.lastTs); }); @@ -915,117 +910,13 @@ Wallet.fromJSON = function fromJSON(json, passphrase) { return new Wallet(Wallet._fromJSON(json, passphrase)); }; -Wallet.prototype.saveFile = function saveFile(callback) { +Wallet.prototype.save = function save(callback) { callback = utils.asyncify(callback); - if (!this.options.file) + if (!this.options.store && !this.options.db) return callback(); - return this.toFile(this.file, this.options.passphrase, callback); -}; - -Wallet.prototype.toFile = function toFile(file, callback) { - var json, options; - - if (typeof file === 'function') { - callback = file; - file = null; - } - - if (!file) - file = this.file; - - callback = utils.asyncify(callback); - - if (!bcoin.fs) - return callback(); - - json = JSON.stringify(this.toJSON(this.options.passphrase), null, 2); - - options = { - encoding: 'utf8', - mode: 0o600 - }; - - fs.writeFile(file, json, options, function(err) { - if (err) - return callback(err); - - return callback(null, file); - }); -}; - -Wallet._fromFile = function _fromFile(file, passphrase, callback) { - if (typeof passphrase === 'function') { - callback = passphrase; - passphrase = null; - } - - callback = utils.asyncify(callback); - - if (!bcoin.fs) - return callback(); - - if (!file) - return callback(); - - fs.readFile(file, 'utf8', function(err, json) { - var options; - - if (err && err.code === 'ENOENT') - return callback(); - - if (err) - return callback(err); - - try { - options = Wallet._fromJSON(JSON.parse(json)); - } catch (e) { - return callback(e); - } - - return callback(null, options); - }); -}; - -Wallet.fromFile = function fromFile(file, passphrase, callback) { - if (typeof passphrase === 'function') { - callback = passphrase; - passphrase = null; - } - - return Wallet._fromFile(file, passphrase, function(err, options) { - if (err) - return callback(err); - - if (!options) - return callback(); - - return callback(null, new Wallet(options)); - }); -}; - -Wallet.load = function load(options, callback) { - var file; - - if (options.id) { - file = bcoin.dir + '/wallet-' - + options.id + '-' + network.type + '.json'; - options.file = true; - } - - if (typeof options.file === 'string') - file = options.file; - - return Wallet.fromFile(file, options.passphrase, function(err, wallet) { - if (err) - return callback(err); - - if (!wallet) - wallet = new Wallet(options); - - return callback(null, wallet); - }); + return this.db.save(this.id, this, callback); }; /** diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js new file mode 100644 index 00000000..c98f32c8 --- /dev/null +++ b/lib/bcoin/walletdb.js @@ -0,0 +1,289 @@ +/** + * walletdb.js - storage for wallets + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * https://github.com/indutny/bcoin + */ + +var inherits = require('inherits'); +var EventEmitter = require('events').EventEmitter; + +var bcoin = require('../bcoin'); +var levelup = require('levelup'); +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; + +/** + * WalletDB + */ + +function WalletDB(options) { + if (!(this instanceof WalletDB)) + return new WalletDB(options); + + if (WalletDB.global) + return WalletDB.global; + + if (!options) + options = {}; + + EventEmitter.call(this); + + this.options = options; + this.file = options.file; + + if (!this.file) + this.file = bcoin.dir + '/wallet-' + network.type + '.db'; + + if (!this.dir) + this.dir = bcoin.dir + '/wallet-' + network.type; + + if (!this.type) + this.type = 'file'; + + WalletDB.global = this; + + this._init(); +} + +inherits(WalletDB, EventEmitter); + +WalletDB._db = {}; + +WalletDB.prototype._init = function _init() { + if (this.type === 'file' && !bcoin.fs) { + this.type = 'leveldb'; + utils.debug('`fs` module not available. Falling back to leveldb.'); + } + + if (this.type === 'file') { + if (bcoin.fs) { + try { + bcoin.fs.statSync(this.dir, 0o750); + } catch (e) { + bcoin.fs.mkdirSync(this.dir); + } + } + if (+process.env.BCOIN_FRESH === 1) { + try { + bcoin.fs.readdirSync(this.dir).forEach(function(file) { + bcoin.fs.unlinkSync(this.dir + '/' + file); + }, this); + } catch (e) { + ; + } + } + return; + } + + if (this.type === 'leveldb') { + if (!WalletDB._db[this.file]) { + WalletDB._db[this.file] = new levelup(this.file, { + keyEncoding: 'ascii', + valueEncoding: 'json', + createIfMissing: true, + errorIfExists: false, + compression: true, + cacheSize: 1 * 1024 * 1024, + writeBufferSize: 1 * 1024 * 1024, + // blockSize: 4 * 1024, + maxOpenFiles: 1024, + // blockRestartInterval: 16, + db: bcoin.isBrowser + ? require('memdown') + : require('level' + 'down') + }); + } + this.db = WalletDB._db[this.file]; + return; + } + + throw new Error('Unknown storage type: ' + this.type); +}; + +WalletDB.prototype.save = function save(id, json, callback) { + callback = utils.asyncify(callback); + + if (this.type === 'leveldb') + return this.saveDB(id, json, callback); + + if (this.type === 'file') + return this.saveFile(id, json, callback); + + throw new Error('Unknown storage type: ' + this.type); +}; + +WalletDB.prototype.saveDB = function saveFile(id, json, callback) { + var key; + + key = 'w/' + id; + + if (json instanceof bcoin.wallet) { + json.store = true; + json.db = this; + json = json.toJSON(this.options.noPool); + } + + callback = utils.asyncify(callback); + + 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: 0o600 + }; + + fs.writeFile(file, json, options, function(err) { + if (err) + return callback(err); + + return callback(null, file); + }); +}; + +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; + } + + if (this.type === 'leveldb') + return this.getDB(id, passphrase, callback); + + if (this.type === 'file') + return this.getFile(id, passphrase, callback); + + throw new Error('Unknown storage type: ' + this.type); +}; + +WalletDB.prototype.getFile = function getFile(id, passphrase, callback) { + var self = this; + var file; + + callback = utils.asyncify(callback); + + if (!bcoin.fs) + return callback(); + + if (!id) + return callback(); + + file = this.dir + '/' + id + '.json'; + + fs.readFile(file, 'utf8', function(err, json) { + var options; + + if (err && err.code === 'ENOENT') + return callback(); + + if (err) + return callback(err); + + try { + options = bcoin.wallet._fromJSON(JSON.parse(json), passphrase); + } catch (e) { + return callback(e); + } + + options.store = true; + options.db = self; + + return callback(null, options); + }); +}; + +WalletDB.prototype.getDB = function getDB(id, passphrase, callback) { + var self = this; + var key; + + callback = utils.asyncify(callback); + + key = 'w/' + id; + + this.db.get(key, function(err, json) { + var options; + + if (err && err.type === 'NotFoundError') + return callback(); + + if (err) + return callback(err); + + try { + options = bcoin.wallet._fromJSON(JSON.parse(json), passphrase); + } catch (e) { + return callback(e); + } + + options.store = true; + options.db = self; + + return callback(null, options); + }); +}; + +WalletDB.prototype.get = function get(id, passphrase, callback) { + callback = utils.asyncify(callback); + return this.getJSON(id, passphrase, function(err, options) { + if (err) + return callback(err); + + if (!options) + return callback(); + + return callback(null, new bcoin.wallet(options)); + }); +}; + +WalletDB.prototype.create = function create(options, callback) { + var self = this; + callback = utils.asyncify(callback); + return this.getJSON(options.id, options.passphrase, function(err, opt) { + if (err) + return callback(err); + + if (!opt) { + options.store = true; + options.db = self; + return callback(null, new bcoin.wallet(options)); + } + + return callback(null, new bcoin.wallet(opt)); + }); +}; + +/** + * Expose + */ + +module.exports = WalletDB;