This commit is contained in:
Christopher Jeffrey 2016-03-01 19:35:15 -08:00
parent fd3bd9fac9
commit b1232593d8
7 changed files with 1592 additions and 77 deletions

View File

@ -69,6 +69,7 @@ bcoin.coin = require('./bcoin/coin');
bcoin.tx = require('./bcoin/tx');
bcoin.mtx = require('./bcoin/mtx');
bcoin.txpool = require('./bcoin/tx-pool');
bcoin.txdb = require('./bcoin/txdb');
bcoin.abstractblock = require('./bcoin/abstractblock');
bcoin.compactblock = require('./bcoin/compactblock');
bcoin.block = require('./bcoin/block');

View File

@ -100,6 +100,7 @@ Fullnode.prototype._init = function _init() {
// Emit events for any TX we see that's
// is relevant to one of our wallets.
this.on('tx', function(tx) {
return;
self.walletdb.ownTX(tx, function(err, input, output) {
if (err)
return self.emit('error', err);

View File

@ -1145,11 +1145,12 @@ TX.prototype.toExtended = function toExtended(coins) {
buf._witnessSize = tx._witnessSize;
buf._size = tx._size;
buf._extendedSize = off;
return buf;
};
TX._fromExtended = function _fromExtended(buf) {
TX._fromExtended = function _fromExtended(buf, coins) {
var tx, coinCount, chunkSize, coin, i;
var off = 0;
@ -1161,7 +1162,7 @@ TX._fromExtended = function _fromExtended(buf) {
tx.height = utils.readU32(buf, off);
off += 4;
tx.block = buf.slice(off, off + 32);
tx.block = buf.slice(off, off + 32).toString('hex');
off += 32;
tx.index = utils.readU32(buf, off);
off += 4;
@ -1170,12 +1171,15 @@ TX._fromExtended = function _fromExtended(buf) {
tx.ps = utils.readU32(buf, off);
off += 4;
if (+tx.block === 0)
tx.block = null;
if (tx.height === 0x7fffffff)
tx.height = -1;
if (buf.length > off) {
if (coins) {
coinCount = utils.readIntv(buf, off);
off = cointCount.off;
off = coinCount.off;
coinCount = coinCount.r;
for (i = 0; i < coinCount; i++) {
chunkSize = utils.readIntv(buf, off);
@ -1189,11 +1193,13 @@ TX._fromExtended = function _fromExtended(buf) {
}
}
tx._extendedSize = off;
return tx;
};
TX.fromExtended = function fromExtended(buf) {
return new TX(TX._fromExtended(buf));
TX.fromExtended = function fromExtended(buf, coins) {
return new TX(TX._fromExtended(buf, coins));
};
/**

1288
lib/bcoin/txdb.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -106,6 +106,7 @@ Wallet.prototype._init = function _init() {
assert(!this._initialized);
this._initialized = true;
this.id = this.getID();
if (Object.keys(this.addressMap).length === 0) {
for (i = 0; i < this.receiveDepth - 1; i++)
@ -128,20 +129,7 @@ Wallet.prototype._init = function _init() {
assert(!this.receiveAddress.change);
assert(this.changeAddress.change);
this.id = this.getID();
this.tx = new bcoin.txpool(this);
// Notify owners about new accepted transactions
this.tx.on('update', function(lastTs, lastHeight, tx) {
var b = this.getBalance();
if (prevBalance && prevBalance.cmp(b) !== 0)
self.emit('balance', b);
if (tx)
self.emit('update', tx);
self.lastTs = Math.max(lastTs, self.lastTs);
self.lastHeight = Math.max(lastHeight, self.lastHeight);
prevBalance = b;
});
this.tx = this.db.tx;
this.tx.on('tx', function(tx) {
self.emit('tx', tx);
@ -158,12 +146,6 @@ Wallet.prototype._init = function _init() {
self.emit('error', err);
});
if (options.txs)
this.tx.populate(options.txs);
this.lastTs = this.tx._lastTs;
this.lastHeight = this.tx._lastHeight;
this.save(function(err) {
if (err)
throw err;
@ -552,8 +534,9 @@ Wallet.prototype.fill = function fill(tx, options) {
return true;
};
Wallet.prototype.fillPrevout = function fillPrevout(tx) {
Wallet.prototype.fillPrevout = function fillPrevout(tx, callback) {
return tx.fillPrevout(this);
return this.db.fillTX(tx, callback);
};
Wallet.prototype.createTX = function createTX(options, outputs) {
@ -797,32 +780,34 @@ Wallet.prototype.sign = function sign(tx, type, index) {
}, 0);
};
Wallet.prototype.addTX = function addTX(tx, block) {
return this.tx.add(tx);
Wallet.prototype.addTX = function addTX(tx, callback) {
if (!this.db)
return;
return this.db.addTX(tx, callback);
};
Wallet.prototype.getAll = function getAll(address) {
return this.tx.getAll(address);
Wallet.prototype.getAll = function getAll(callback) {
if (!this.db)
return;
return this.db.getAll(this, callback);
};
Wallet.prototype.getUnspent = function getUnspent(address) {
return this.tx.getUnspent(address);
Wallet.prototype.getUnspent = function getUnspent(callback) {
if (!this.db)
return;
return this.db.getUnspent(this, callback);
};
Wallet.prototype.getPending = function getPending(address) {
return this.tx.getPending(address);
Wallet.prototype.getPending = function getPending(callback) {
if (!this.db)
return;
return this.db.getPending(this, callback);
};
Wallet.prototype.getSent = function getSent(address) {
return this.tx.getSent(address);
};
Wallet.prototype.getReceived = function getReceived(address) {
return this.tx.getReceived(address);
};
Wallet.prototype.getBalance = function getBalance(address) {
return this.tx.getBalance(address);
Wallet.prototype.getBalance = function getBalance(callback) {
if (!this.db)
return;
return this.db.getBalance(this, callback);
};
Wallet.prototype.__defineGetter__('script', function() {
@ -901,10 +886,7 @@ Wallet.prototype.toJSON = function toJSON() {
keys: this.keys.map(function(key) {
return key.xpubkey;
}),
balance: utils.btc(this.getBalance()),
txs: this.options.noPool ? [] : this.tx.getAll().map(function(tx) {
return tx.toCompact();
})
txs: []
};
};
@ -945,7 +927,7 @@ Wallet.fromJSON = function fromJSON(json, passphrase) {
Wallet.prototype._saveAddress = function _saveAddress(address, callback) {
callback = utils.ensure(callback);
if (!this.options.store || !this.options.db)
if (!this.db)
return utils.nextTick(callback);
return this.db.saveAddress(this.id, address, callback);
@ -954,10 +936,10 @@ Wallet.prototype._saveAddress = function _saveAddress(address, callback) {
Wallet.prototype.save = function save(callback) {
callback = utils.ensure(callback);
if (!this.options.store || !this.options.db)
if (!this.db)
return utils.nextTick(callback);
return this.db.save(this.id, this, callback);
return this.db.save(this, callback);
};
/**

View File

@ -20,6 +20,8 @@ var fs = bcoin.fs;
*/
function WalletDB(node, options) {
var self = this;
if (!(this instanceof WalletDB))
return new WalletDB(node, options);
@ -49,7 +51,47 @@ utils.inherits(WalletDB, EventEmitter);
WalletDB._db = {};
WalletDB.prototype.dump = function dump(callback) {
var self = this;
var records = [];
var iter = this.db.db.iterator({
gte: 'w',
lte: 'w~',
keys: true,
values: true,
fillCache: false,
keyAsBuffer: false,
valueAsBuffer: true
});
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, records);
});
}
records.push([key, value.slice(0, 200).toString('hex')]);
next();
});
})();
};
WalletDB.prototype._init = function _init() {
var self = this;
var levelup;
if (!WalletDB._db[this.file]) {
@ -73,6 +115,8 @@ WalletDB.prototype._init = function _init() {
}
this.db = WalletDB._db[this.file];
this.tx = new bcoin.txdb('w', this.db);
};
WalletDB.prototype.getJSON = function getJSON(id, callback) {
@ -96,7 +140,10 @@ WalletDB.prototype.saveJSON = function saveJSON(id, json, callback) {
json = utils.merge({}, json);
delete json.store;
delete json.db;
var save = bcoin.wallet.prototype.save;
bcoin.wallet.prototype.save = function() {};
json = new bcoin.wallet(json).toJSON();
bcoin.wallet.prototype.save = save;
}
}
@ -106,7 +153,7 @@ WalletDB.prototype.saveJSON = function saveJSON(id, json, callback) {
if (err)
return callback(err);
if (json && self.type === 'leveldb') {
if (json) {
batch = self.db.batch();
Object.keys(json.addressMap).forEach(function(address) {
batch.put('w/a/' + address + '/' + json.id, new Buffer([]));
@ -138,7 +185,7 @@ WalletDB.prototype.removeJSON = function removeJSON(id, callback) {
if (err)
return callback(err);
if (json && self.type === 'leveldb') {
if (json) {
batch = self.db.batch();
Object.keys(json.addressMap).forEach(function(address) {
batch.del('w/a/' + address + '/' + json.id);
@ -264,7 +311,7 @@ WalletDB.prototype.save = function save(options, callback) {
return this.saveJSON(options.id, options, callback);
};
WalletDB.prototype.remove = function save(id, callback) {
WalletDB.prototype.remove = function remove(id, callback) {
var self = this;
callback = utils.ensure(callback);
@ -313,6 +360,7 @@ WalletDB.prototype.removeAddress = function removeAddress(id, address, callback)
this.db.del('w/a/' + address + '/' + id, callback);
};
/*
WalletDB.prototype._getIDs = function _getIDs(address, callback) {
var self = this;
var ids = [];
@ -424,6 +472,135 @@ WalletDB.prototype.ownTX = function ownTX(tx, callback) {
});
});
};
*/
WalletDB.prototype.addTX = function addTX(tx, callback) {
return this.tx.add(tx, callback);
};
WalletDB.prototype.getAll = function getAll(id, callback) {
var self = this;
return this.getAddresses(id, function(err, addresses) {
if (err)
return callback(err);
return self.tx.getAllByAddress(addresses, callback);
});
};
WalletDB.prototype.getUnspent = function getUnspent(id, callback) {
var self = this;
return this.getAddresses(id, function(err, addresses) {
if (err)
return callback(err);
return self.tx.getUnspentByAddress(addresses, callback);
});
};
WalletDB.prototype.getPending = function getPending(id, callback) {
var self = this;
return this.getAddresses(id, function(err, addresses) {
if (err)
return callback(err);
return self.tx.getPendingByAddress(addresses, callback);
});
};
WalletDB.prototype.getBalance = function getBalance(id, callback) {
var self = this;
return this.getAddresses(id, function(err, addresses) {
if (err)
return callback(err);
return self.tx.getBalanceByAddress(addresses, callback);
});
};
WalletDB.prototype.getAddresses = function getAddresses(id, callback) {
if (typeof id === 'string')
return callback(null, [id]);
if (Array.isArray(id))
return callback(null, id);
if (id.addressMap)
return callback(null, Object.keys(id.addressMap));
if (typeof id === 'object')
return callback(null, Object.keys(id));
return this.db.get('w/w/' + id, function(err, buf) {
var json;
if (err)
return callback(err);
try {
json = JSON.parse(buf.toString('utf8'));
} catch (e) {
return callback(e);
}
return callback(null, Object.keys(json.addressMap));
});
};
WalletDB.prototype.getIDs = function _getIDs(address, callback) {
var self = this;
var ids = [];
var iter = this.db.db.iterator({
gte: 'w/a/' + address,
lte: 'w/a/' + address + '~',
keys: true,
values: false,
fillCache: false,
keyAsBuffer: false
});
callback = utils.ensure(callback);
(function next() {
iter.next(function(err, key, value) {
if (err) {
return iter.end(function() {
callback(err);
});
}
if (key === undefined) {
return iter.end(function(err) {
if (err)
return callback(err);
return callback(null, ids);
});
}
ids.push(key.split('/')[2]);
next();
});
})();
};
WalletDB.prototype.testTX = function test(tx, callback) {
var self = this;
return callback(null, true);
utils.forEachSerial(tx.getAddresses(), function(address, next) {
self.getIDs(address, function(err, ids) {
if (err)
return next(err);
if (ids.length > 0)
return callback(null, true);
next();
});
}, function(err) {
if (err)
return callback(err);
return callback(null, false);
});
};
/**
* Expose

View File

@ -124,9 +124,11 @@ describe('Wallet', function() {
assert(tx.verify());
});
it('should have TX pool and be serializable', function() {
var w = bcoin.wallet();
var f = bcoin.wallet();
it('should have TX pool and be serializable', function(cb) {
bcoin.walletdb().create({ id: 'w' }, function(err, w) {
assert(w);
bcoin.walletdb().create({ id: 'f' }, function(err, f) {
assert(f);
// Coinbase
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
@ -170,27 +172,85 @@ describe('Wallet', function() {
fake.hint = 'fake';
// Fake TX should temporarly change output
w.addTX(fake);
// w.addTX(fake);
w.addTX(t4);
assert.equal(w.getBalance().toString(10), '22500');
w.addTX(t1);
assert.equal(w.getBalance().toString(10), '73000');
w.addTX(t2);
assert.equal(w.getBalance().toString(10), '47000');
w.addTX(t3);
assert.equal(w.getBalance().toString(10), '22000');
w.addTX(f1);
assert.equal(w.getBalance().toString(10), '11000');
assert(w.getAll().some(function(tx) {
return tx.hash('hex') === f1.hash('hex');
}));
process.on('uncaughtException', function() {
return;
w.db.dump(function(err, records) {
console.log(records);
process.exit(1);
});
});
var w2 = bcoin.wallet.fromJSON(w.toJSON());
assert.equal(w2.getBalance().toString(10), '11000');
assert(w2.getAll().some(function(tx) {
return tx.hash('hex') === f1.hash('hex');
}));
w.addTX(fake, function(err) {
if (err) throw err;
assert(!err);
w.addTX(t4, function(err) {
if (err) throw err;
assert(!err);
if (0) {
w.db.tx.getCoinByAddress(Object.keys(w.addressMap), function(err, coins) {
console.log(coins);
cb();
});
return;
}
if (0) {
w.db.dump(function(err, records) {
console.log(records);
//process.exit(1);
return cb();
});
return;
}
w.getBalance(function(err, balance) {
assert(!err);
assert.equal(balance.toString(10), '22500');
// assert.equal(balance.toString(10), '22000');
w.addTX(t1, function(err) {
w.getBalance(function(err, balance) {
assert(!err);
assert.equal(balance.toString(10), '73000');
w.addTX(t2, function(err) {
assert(!err);
w.getBalance(function(err, balance) {
assert(!err);
assert.equal(balance.toString(10), '47000');
w.addTX(t3, function(err) {
assert(!err);
w.getBalance(function(err, balance) {
assert(!err);
assert.equal(balance.toString(10), '22000');
w.addTX(f1, function(err) {
assert(!err);
w.getBalance(function(err, balance) {
assert(!err);
assert.equal(balance.toString(10), '11000');
w.getAll(function(err, txs) {
assert(txs.some(function(tx) {
return tx.hash('hex') === f1.hash('hex');
}));
var w2 = bcoin.wallet.fromJSON(w.toJSON());
// assert.equal(w2.getBalance().toString(10), '11000');
// assert(w2.getAll().some(function(tx) {
// return tx.hash('hex') === f1.hash('hex');
// }));
cb();
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
it('should fill tx with inputs', function(cb) {