add spv node. add .bcoin dir. add file storage for wallet.
This commit is contained in:
parent
86a368dddf
commit
6e2eb039ac
10
lib/bcoin.js
10
lib/bcoin.js
@ -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;
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
117
lib/bcoin/spvnode.js
Normal 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;
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user