add spv node. add .bcoin dir. add file storage for wallet.

This commit is contained in:
Christopher Jeffrey 2016-02-20 16:48:07 -08:00
parent 86a368dddf
commit 6e2eb039ac
13 changed files with 537 additions and 150 deletions

View File

@ -20,6 +20,8 @@ if (process.env.BCOIN_DEBUG) {
bcoin.debug = +bcoin.debug === 1;
}
bcoin.dir = process.env.HOME + '/.bcoin';
if (!bcoin.isBrowser) {
bcoin.fs = require('f' + 's');
bcoin.crypto = require('cry' + 'pto');
@ -27,6 +29,14 @@ if (!bcoin.isBrowser) {
bcoin.cp = require('child_' + 'process');
}
if (bcoin.fs) {
try {
bcoin.fs.statSync(bcoin.dir, 0o750);
} catch (e) {
bcoin.fs.mkdirSync(bcoin.dir);
}
}
bcoin.inherits = inherits;
bcoin.elliptic = elliptic;
bcoin.bn = bn;

View File

@ -31,7 +31,6 @@ function Address(options) {
options = {};
this.options = options;
this.storage = options.storage;
this.label = options.label || '';
this.derived = !!options.derived;
this.addressMap = null;
@ -67,8 +66,6 @@ function Address(options) {
if (options.redeem || options.script)
this.setRedeem(options.redeem || options.script);
this.prefix = 'bt/address/' + this.getID() + '/';
}
inherits(Address, EventEmitter);
@ -559,7 +556,7 @@ Address.getType = function getType(addr) {
return prefix;
};
Address.prototype.toJSON = function toJSON(encrypt) {
Address.prototype.toJSON = function toJSON(passphrase) {
return {
v: 1,
name: 'address',
@ -570,7 +567,7 @@ Address.prototype.toJSON = function toJSON(encrypt) {
index: this.index,
path: this.path,
address: this.getAddress(),
key: this.key.toJSON(encrypt),
key: this.key.toJSON(passphrase),
type: this.type,
redeem: this.redeem ? utils.toHex(this.redeem) : null,
keys: this.keys.map(utils.toBase58),
@ -579,7 +576,7 @@ Address.prototype.toJSON = function toJSON(encrypt) {
};
};
Address.fromJSON = function fromJSON(json, decrypt) {
Address.fromJSON = function fromJSON(json, passphrase) {
var w;
assert.equal(json.v, 1);
@ -594,7 +591,7 @@ Address.fromJSON = function fromJSON(json, decrypt) {
derived: json.derived,
index: json.index,
path: json.path,
key: bcoin.keypair.fromJSON(json.key, decrypt),
key: bcoin.keypair.fromJSON(json.key, passphrase),
type: json.type,
redeem: json.redeem ? utils.toArray(json.redeem, 'hex') : null,
keys: json.keys.map(utils.fromBase58),

View File

@ -31,7 +31,7 @@ function BlockDB(options) {
this.file = options.indexFile;
if (!this.file)
this.file = process.env.HOME + '/bcoin-index-' + network.type + '.db';
this.file = bcoin.dir + '/index-' + network.type + '.db';
this.options = options;
@ -1095,7 +1095,7 @@ function BlockData(options) {
this.file = options.blockFile;
if (!this.file)
this.file = process.env.HOME + '/bcoin-block-' + network.type + '.db';
this.file = bcoin.dir + '/block-' + network.type + '.db';
this.bufferPool = { used: {} };
this.size = 0;

View File

@ -752,10 +752,14 @@ Chain.prototype._addEntry = function _addEntry(entry, block, callback) {
};
Chain.prototype.resetHeight = function resetHeight(height, force) {
var self = this;
if (height === this.db.getSize() - 1)
return;
this.db.resetHeightSync(height);
this.db.resetHeightSync(height, function(entry) {
self.emit('remove entry', entry);
});
// Reset the orphan map completely. There may
// have been some orphans on a forked chain we
@ -791,6 +795,8 @@ Chain.prototype.resetHeightAsync = function resetHeightAsync(height, callback, f
self.purgePending();
return done();
}, function(entry) {
self.emit('remove entry', entry);
});
};

View File

@ -35,7 +35,7 @@ function ChainDB(chain, options) {
this.file = options.file;
if (!this.file)
this.file = process.env.HOME + '/bcoin-chain-' + network.type + '.db';
this.file = bcoin.dir + '/chain-' + network.type + '.db';
this.heightLookup = {};
this.queue = {};
@ -426,7 +426,7 @@ ChainDB.prototype.saveAsync = function saveAsync(entry, callback) {
});
};
ChainDB.prototype.resetHeightSync = function resetHeightSync(height) {
ChainDB.prototype.resetHeightSync = function resetHeightSync(height, emit) {
var self = this;
var osize = this.size;
var ohighest = this.highest;
@ -453,6 +453,10 @@ ChainDB.prototype.resetHeightSync = function resetHeightSync(height) {
assert(existing);
// Emit the blocks we remove.
if (emit)
emit(existing);
// Warn of potential race condition
// (handled with _onFlush).
if (this.queue[i])
@ -489,7 +493,7 @@ ChainDB.prototype.resetHeightSync = function resetHeightSync(height) {
});
};
ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback) {
ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback, emit) {
var self = this;
var osize = this.size;
var ohighest = this.highest;
@ -538,6 +542,10 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback)
assert(existing);
// Emit the blocks we remove.
if (emit)
emit(existing);
// Warn of potential race condition
// (handled with _onFlush).
if (self.queue[i])

View File

@ -837,25 +837,25 @@ HDPrivateKey.prototype.deriveString = function deriveString(path) {
}, this);
};
HDPrivateKey.prototype.toJSON = function toJSON(encrypt) {
HDPrivateKey.prototype.toJSON = function toJSON(passphrase) {
var json = {
v: 1,
name: 'hdkey',
encrypted: encrypt ? true : false
encrypted: passphrase ? true : false
};
if (this instanceof HDPrivateKey) {
if (this.seed) {
json.mnemonic = encrypt
? encrypt(this.seed.mnemonic)
json.mnemonic = passphrase
? utils.encrypt(this.seed.mnemonic, passphrase)
: this.seed.mnemonic;
json.passphrase = encrypt
? encrypt(this.seed.passphrase)
json.passphrase = passphrase
? utils.encrypt(this.seed.passphrase, passphrase)
: this.seed.passphrase;
return json;
}
json.xprivkey = encrypt
? encrypt(this.xprivkey)
json.xprivkey = passphrase
? utils.encrypt(this.xprivkey, passphrase)
: this.xprivkey;
return json;
}
@ -865,21 +865,21 @@ HDPrivateKey.prototype.toJSON = function toJSON(encrypt) {
return json;
};
HDPrivateKey.fromJSON = function fromJSON(json, decrypt) {
HDPrivateKey.fromJSON = function fromJSON(json, passphrase) {
assert.equal(json.v, 1);
assert.equal(json.name, 'hdkey');
if (json.encrypted && !decrypt)
if (json.encrypted && !passphrase)
throw new Error('Cannot decrypt address');
if (json.mnemonic) {
return new HDPrivateKey({
seed: new HDSeed({
mnemonic: json.encrypted
? decrypt(json.mnemonic)
? utils.decrypt(json.mnemonic, passphrase)
: json.mnemonic,
passphrase: json.encrypted
? decrypt(json.passphrase)
? utils.decrypt(json.passphrase, passphrase)
: json.passphrase
})
});
@ -888,7 +888,7 @@ HDPrivateKey.fromJSON = function fromJSON(json, decrypt) {
if (json.xprivkey) {
return new HDPrivateKey({
xkey: json.encrypted
? decrypt(json.xprivkey)
? utils.decrypt(json.xprivkey, passphrase)
: json.xprivkey
});
}

View File

@ -195,16 +195,16 @@ KeyPair.sign = function sign(msg, key) {
return bcoin.ecdsa.sign(msg, key.priv);
};
KeyPair.prototype.toJSON = function toJSON(encrypt) {
KeyPair.prototype.toJSON = function toJSON(passphrase) {
var json = {
v: 1,
name: 'keypair',
encrypted: encrypt ? true : false
encrypted: passphrase ? true : false
};
if (this.pair.priv) {
json.privateKey = encrypt
? encrypt(this.toSecret())
json.privateKey = passphrase
? utils.encrypt(this.toSecret(), passphrase)
: this.toSecret();
return json;
}
@ -214,19 +214,19 @@ KeyPair.prototype.toJSON = function toJSON(encrypt) {
return json;
};
KeyPair.fromJSON = function fromJSON(json, decrypt) {
KeyPair.fromJSON = function fromJSON(json, passphrase) {
var privateKey, publicKey, compressed;
assert.equal(json.v, 1);
assert.equal(json.name, 'keypair');
if (json.encrypted && !decrypt)
if (json.encrypted && !passphrase)
throw new Error('Cannot decrypt address');
if (json.privateKey) {
privateKey = json.privateKey;
if (json.encrypted)
privateKey = decrypt(privateKey);
privateKey = utils.decrypt(privateKey, passphrase);
return KeyPair.fromSecret(privateKey);
}

View File

@ -40,6 +40,9 @@ function Node(options) {
this.pool = null;
this.chain = null;
this.miner = null;
this.wallet = null;
this.loading = false;
Node.global = this;
@ -51,9 +54,14 @@ inherits(Node, EventEmitter);
Node.prototype._init = function _init() {
var self = this;
this.loading = true;
if (!this.options.pool)
this.options.pool = {};
if (!this.options.miner)
this.options.miner = {};
this.blockdb = new bcoin.blockdb(this.options.blockdb);
this.mempool = new bcoin.mempool(this, this.options.mempool);
@ -63,6 +71,7 @@ Node.prototype._init = function _init() {
this.pool = new bcoin.pool(this.options.pool);
this.chain = this.pool.chain;
this.miner = new bcoin.miner(this.pool, this.options.miner);
this.mempool.on('error', function(err) {
@ -73,7 +82,51 @@ Node.prototype._init = function _init() {
self.emit('error', err);
});
this.pool.startSync();
if (!this.options.wallet)
this.options.wallet = {};
if (!this.options.wallet.id)
this.options.wallet.id = 'primary';
bcoin.wallet.load(this.options.wallet, function(err, wallet) {
if (err)
throw err;
self.wallet = 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.mempool.on('tx', function(tx) {
self.wallet.addTX(tx);
});
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) {
if (err)
throw err;
self.pool.startSync();
self.loading = false;
self.emit('load');
});
});
};
Node.prototype.getCoin = function getCoin(hash, index, callback) {

View File

@ -1185,11 +1185,11 @@ Pool.prototype.isWatched = function(tx, bloom) {
return false;
};
Pool.prototype.addWallet = function addWallet(wallet) {
Pool.prototype.addWallet = function addWallet(wallet, callback) {
var self = this;
if (this.loading)
return this.once('load', this.addWallet.bind(this, wallet));
return this.once('load', this.addWallet.bind(this, wallet, callback));
if (this.wallets.indexOf(wallet) !== -1)
return false;
@ -1206,16 +1206,16 @@ Pool.prototype.addWallet = function addWallet(wallet) {
});
if (!self.options.spv)
return;
return callback();
if (self._pendingSearch)
return;
return callback();
self._pendingSearch = true;
utils.nextTick(function() {
self._pendingSearch = false;
self.searchWallet();
self.searchWallet(callback);
});
}
@ -1288,7 +1288,7 @@ Pool.prototype.unwatchWallet = function unwatchWallet(wallet) {
delete wallet._poolOnRemove;
};
Pool.prototype.searchWallet = function(ts, height) {
Pool.prototype.searchWallet = function(ts, height, callback) {
var self = this;
var wallet;
@ -1297,7 +1297,8 @@ Pool.prototype.searchWallet = function(ts, height) {
if (!this.options.spv)
return;
if (ts == null) {
if (ts == null || typeof ts === 'function') {
callback = ts;
height = this.wallets.reduce(function(height, wallet) {
if (wallet.lastHeight < height)
return wallet.lastHeight;
@ -1311,6 +1312,7 @@ Pool.prototype.searchWallet = function(ts, height) {
}, Infinity);
assert(ts !== Infinity);
} else if (typeof ts !== 'number') {
callback = height;
wallet = ts;
if (wallet.loading) {
wallet.once('load', function() {
@ -1322,6 +1324,8 @@ Pool.prototype.searchWallet = function(ts, height) {
height = wallet.lastHeight;
}
callback = utils.asyncify(callback);
// Always prefer height
if (height > 0) {
// Back one week
@ -1329,8 +1333,10 @@ Pool.prototype.searchWallet = function(ts, height) {
height = this.chain.height - (7 * 24 * 6);
this.chain.resetHeightAsync(height, function(err) {
if (err)
throw err;
if (err) {
utils.debug('Failed to reset height: %s', err.stack + '');
return callback(err);
}
utils.debug('Wallet height: %s', height);
utils.debug(
@ -1338,6 +1344,8 @@ Pool.prototype.searchWallet = function(ts, height) {
self.chain.height,
new Date(self.chain.tip.ts * 1000)
);
callback();
});
return;
@ -1347,8 +1355,10 @@ Pool.prototype.searchWallet = function(ts, height) {
ts = utils.now() - 7 * 24 * 3600;
this.chain.resetTimeAsync(ts, function(err) {
if (err)
throw err;
if (err) {
utils.debug('Failed to reset time: %s', err.stack + '');
return callback(err);
}
utils.debug('Wallet time: %s', new Date(ts * 1000));
utils.debug(
@ -1356,6 +1366,8 @@ Pool.prototype.searchWallet = function(ts, height) {
self.chain.height,
new Date(self.chain.tip.ts * 1000)
);
callback();
});
};

117
lib/bcoin/spvnode.js Normal file
View File

@ -0,0 +1,117 @@
/**
* spvnode.js - spv node for bcoin
* 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 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;
/**
* SPVNode
*/
function SPVNode(options) {
if (!(this instanceof SPVNode))
return new SPVNode(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.pool = null;
this.chain = null;
this.wallet = null;
this.loading = false;
SPVNode.global = this;
this._init();
}
inherits(SPVNode, EventEmitter);
SPVNode.prototype._init = function _init() {
var self = this;
this.loading = true;
if (!this.options.pool)
this.options.pool = {};
this.options.pool.spv = true;
this.options.pool.preload = this.options.pool.preload !== false;
this.pool = new bcoin.pool(this.options.pool);
this.chain = this.pool.chain;
this.pool.on('error', function(err) {
self.emit('error', err);
});
this.pool.on('tx', function(tx) {
self.wallet.addTX(tx);
});
if (!this.options.wallet)
this.options.wallet = {};
if (!this.options.wallet.id)
this.options.wallet.id = 'primary';
bcoin.wallet.load(this.options.wallet, function(err, wallet) {
if (err)
throw err;
self.wallet = wallet;
utils.debug('Loaded wallet with id=%s address=%s',
wallet.getID(), 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.startSync();
self.loading = false;
self.emit('load');
return;
self.pool.addWallet(this.wallet, function(err) {
if (err)
throw err;
self.pool.startSync();
self.loading = false;
self.emit('load');
});
});
};
/**
* Expose
*/
module.exports = SPVNode;

View File

@ -15,17 +15,15 @@ var EventEmitter = require('events').EventEmitter;
* TXPool
*/
function TXPool(wallet) {
function TXPool(wallet, txs) {
var self = this;
if (!(this instanceof TXPool))
return new TXPool(wallet);
return new TXPool(wallet, txs);
EventEmitter.call(this);
this._wallet = wallet;
this._storage = wallet.storage;
this._prefix = wallet.prefix + 'tx/';
this._all = {};
this._unspent = {};
this._orphans = {};
@ -37,44 +35,29 @@ function TXPool(wallet) {
this._received = new bn(0);
this._balance = new bn(0);
// Load TXs from storage
this._init();
this._init(txs);
}
inherits(TXPool, EventEmitter);
TXPool.prototype._init = function init() {
TXPool.prototype._init = function _init(txs) {
var self = this;
if (!this._storage) {
utils.nextTick(function() {
self._loaded = true;
self.emit('load', self._lastTs, self._lastHeight);
});
if (!txs)
return;
}
var s = this._storage.createReadStream({
keys: false,
start: this._prefix,
end: this._prefix + 'z'
});
s.on('data', function(data) {
self.add(bcoin.tx.fromJSON(data), true);
});
s.on('error', function(err) {
self.emit('error', err);
});
s.on('end', function() {
self._loaded = true;
self.emit('load', self._lastTs, self._lastHeight);
utils.nextTick(function() {
self.populate(txs);
});
};
TXPool.prototype.add = function add(tx, noWrite, strict) {
TXPool.prototype.populate = function populate(txs) {
txs.forEach(function(tx) {
this.add(tx, true);
}, this);
};
TXPool.prototype.add = function add(tx, noWrite) {
var hash = tx.hash('hex');
var updated = false;
var i, j, input, output, coin, unspent, index, orphan;
@ -103,7 +86,7 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
var key = hash + '/' + i;
if (this._unspent[key])
this._unspent[key].height = tx.height;
});
}, this);
this._storeTX(hash, tx, noWrite);
this._lastTs = Math.max(tx.ts, this._lastTs);
this._lastHeight = Math.max(tx.height, this._lastHeight);
@ -226,21 +209,18 @@ TXPool.prototype.getCoin = function getCoin(hash, index) {
TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) {
var self = this;
if (!this._storage || noWrite)
if (noWrite)
return;
this._storage.put(this._prefix + hash, tx.toJSON(), function(err) {
if (err)
self.emit('error', err);
});
this._wallet.saveFile();
};
TXPool.prototype._removeTX = function _removeTX(tx, noWrite) {
var self = this;
var hash = tx.hash('hex');
var key;
var key, i;
for (var i = 0; i < tx.outputs.length; i++) {
for (i = 0; i < tx.outputs.length; i++) {
key = hash + '/' + i;
if (this._unspent[key]) {
delete this._unspent[key];
@ -250,13 +230,73 @@ TXPool.prototype._removeTX = function _removeTX(tx, noWrite) {
// delete this._all[hash];
if (!this._storage || noWrite)
if (noWrite)
return;
this._storage.del(this._prefix + tx.hash('hex'), function(err) {
if (err)
self.emit('error', err);
});
this._wallet.saveFile();
};
TXPool.prototype.removeTX = function removeTX(hash) {
var tx, input, prev, updated;
if (hash.hash)
hash = hash('hex');
tx = this._all[hash];
if (!tx)
return false;
this._removeTX(tx, false);
delete this._all[hash];
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (!input.output || !this._wallet.ownOutput(input.output))
continue;
this._removeInput(input);
this._unspent[key] = input.output;
updated = true;
}
if (updated)
this.emit('update', this._lastTs, this._lastHeight);
};
TXPool.prototype.unconfirm = function unconfirm(hash) {
var tx;
if (hash.hash)
hash = hash('hex');
tx = this._all[hash];
if (!tx)
return false;
if (this._lastHeight >= tx.height)
this._lastHeight = tx.height;
if (this._lastTs >= tx.ts)
this._lastTs = tx.ts;
tx.ps = utils.now();
tx.ts = 0;
tx.block = null;
tx.height = -1;
tx.outputs.forEach(function(output, i) {
var key = hash + '/' + i;
if (this._unspent[key])
this._unspent[key].height = -1;
}, this);
this._storeTX(hash, tx, noWrite);
this._lastTs = Math.max(tx.ts, this._lastTs);
this._lastHeight = Math.max(tx.height, this._lastHeight);
this.emit('update', this._lastTs, this._lastHeight, tx);
this.emit('unconfirmed', tx);
};
TXPool.prototype._addOutput = function _addOutput(tx, i, remove) {
@ -426,42 +466,6 @@ TXPool.prototype.unspent = TXPool.prototype.getUnspent;
TXPool.prototype.pending = TXPool.prototype.getPending;
TXPool.prototype.balance = TXPool.prototype.getBalance;
TXPool.prototype.toJSON = function toJSON() {
return {
v: 1,
type: 'tx-pool',
txs: Object.keys(this._all).map(function(hash) {
return this._all[hash].toJSON();
}, this)
};
};
TXPool.prototype.fromJSON = function fromJSON(json) {
assert.equal(json.v, 1);
assert.equal(json.type, 'tx-pool');
json.txs.forEach(function(tx) {
this.add(bcoin.tx.fromJSON(tx));
}, this);
};
TXPool.fromJSON = function fromJSON(wallet, json) {
var txPool;
assert.equal(json.v, 1);
assert.equal(json.type, 'tx-pool');
txPool = new TXPool(wallet);
utils.nextTick(function() {
json.txs.forEach(function(tx) {
txPool.add(bcoin.tx.fromJSON(tx));
});
});
return txPool;
};
/**
* Expose
*/

View File

@ -217,6 +217,57 @@ utils.sha512hmac = function sha512hmac(data, salt) {
return utils.toArray(result);
};
utils.salt = 'bcoin:';
utils.encrypt = function encrypt(data, passphrase) {
var cipher, out;
if (!bcoin.crypto)
return data;
if (data[0] === ':')
return data;
if (!passphrase)
throw new Error('No passphrase.');
cipher = bcoin.crypto.createCipher('aes-256-cbc', passphrase);
out = '';
out += cipher.update(utils.salt + data, 'utf8', 'hex');
out += cipher.final('hex');
return ':' + out;
};
utils.decrypt = function decrypt(data, passphrase) {
var decipher, out;
if (!bcoin.crypto)
return data;
if (data[0] !== ':')
return data;
if (!passphrase)
throw new Error('No passphrase.');
data = data.substring(1);
decipher = bcoin.crypto.createDecipher('aes-256-cbc', passphrase);
out = '';
out += decipher.update(data, 'hex', 'utf8');
out += decipher.final('utf8');
if (out.indexOf(utils.salt) !== 0)
throw new Error('Decrypt failed.');
out = out.substring(utils.salt.length);
return out;
};
utils.writeAscii = function writeAscii(dst, str, off) {
var i = 0;
var c;

View File

@ -13,12 +13,14 @@ var utils = bcoin.utils;
var assert = utils.assert;
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var fs = bcoin.fs;
/**
* Wallet
*/
function Wallet(options) {
var self = this;
var key, receiving;
if (!(this instanceof Wallet))
@ -87,7 +89,6 @@ function Wallet(options) {
: this.master.deriveAccount44(this.accountIndex);
}
this.storage = options.storage;
this.loading = true;
this.lastTs = 0;
this.lastHeight = 0;
@ -131,15 +132,16 @@ Wallet.prototype._init = function _init() {
assert(!this.receiveAddress.change);
assert(this.changeAddress.change);
this.prefix = 'bt/wallet/' + this.getID() + '/';
this.id = this.getID();
this.file = options.file;
this.tx = options.tx || bcoin.txPool(this);
if (this.tx._loaded) {
this.loading = false;
return;
if (!this.file || this.file === true) {
this.file = bcoin.dir + '/wallet-'
+ this.id + '-' + network.type + '.json';
}
this.tx = new bcoin.txPool(this);
// Notify owners about new accepted transactions
this.tx.on('update', function(lastTs, lastHeight, tx) {
var b = this.getBalance();
@ -163,16 +165,22 @@ Wallet.prototype._init = function _init() {
self.emit('confirmed', tx);
});
this.tx.once('load', function(ts, height) {
self.loading = false;
self.lastTs = ts;
self.lastHeight = height;
self.emit('load', ts);
});
this.tx.on('error', function(err) {
self.emit('error', err);
});
if (options.txs)
this.tx.populate(options.txs);
this.lastTs = this.tx._lastTs;
this.lastHeight = this.tx._lastHeight;
this.saveFile();
utils.nextTick(function() {
self.loading = false;
self.emit('load', self.lastTs);
});
};
Wallet.prototype.addKey = function addKey(key) {
@ -277,6 +285,9 @@ Wallet.prototype._finalizeKeys = function _finalizeKeys(key) {
// bip45: Purpose key address
// bip44: Account key address
Wallet.prototype.getID = function getID() {
if (this.options.id)
return this.options.id;
return bcoin.address.compile(this.accountKey.publicKey);
};
@ -846,11 +857,12 @@ Wallet.prototype.__defineGetter__('address', function() {
return this.getAddress();
});
Wallet.prototype.toJSON = function toJSON(encrypt) {
Wallet.prototype.toJSON = function toJSON(noPool) {
return {
v: 3,
name: 'wallet',
network: network.type,
id: this.id,
type: this.type,
m: this.m,
n: this.n,
@ -859,17 +871,19 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
master: this.master ? this.master.toJSON(encrypt) : null,
master: this.master.toJSON(this.options.passphrase),
addressMap: this.addressMap,
keys: this.keys.map(function(key) {
return key.xpubkey;
}),
balance: utils.btc(this.getBalance()),
tx: this.tx.toJSON()
txs: noPool ? [] : this.tx.getAll().map(function(tx) {
return tx.toJSON();
})
};
};
Wallet.fromJSON = function fromJSON(json, decrypt) {
Wallet._fromJSON = function _fromJSON(json, passphrase) {
var wallet;
assert.equal(json.v, 3);
@ -878,7 +892,8 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
if (json.network)
assert.equal(json.network, network.type);
wallet = new Wallet({
return {
id: json.id,
type: json.type,
m: json.m,
n: json.n,
@ -887,16 +902,130 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
accountIndex: json.accountIndex,
receiveDepth: json.receiveDepth,
changeDepth: json.changeDepth,
master: json.master
? bcoin.hd.fromJSON(json.master, decrypt)
: null,
master: bcoin.hd.fromJSON(json.master, passphrase),
addressMap: json.addressMap,
keys: json.keys
keys: json.keys,
txs: json.txs.map(function(json) {
return bcoin.tx.fromJSON(json);
})
};
};
Wallet.fromJSON = function fromJSON(json, passphrase) {
return new Wallet(Wallet._fromJSON(json, passphrase));
};
Wallet.prototype.saveFile = function saveFile(callback) {
callback = utils.asyncify(callback);
if (!this.options.file)
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.tx.fromJSON(json.tx);
Wallet._fromFile = function _fromFile(file, passphrase, callback) {
if (typeof passphrase === 'function') {
callback = passphrase;
passphrase = null;
}
return wallet;
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);
});
};
/**