walletdb: store chain by height.

This commit is contained in:
Christopher Jeffrey 2016-10-22 07:56:54 -07:00
parent 282a8f7bb4
commit 293bf20b9c
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
6 changed files with 376 additions and 150 deletions

View File

@ -23,6 +23,7 @@ var Coin = require('../primitives/coin');
var TX = require('../primitives/tx');
var Address = require('../primitives/address');
var ChainEntry = require('./chainentry');
var U32 = utils.U32;
var DUMMY = new Buffer([0]);
/*
@ -1135,11 +1136,8 @@ ChainDB.prototype.save = co(function* save(entry, block, view) {
ChainDB.prototype._save = co(function* save(entry, block, view) {
var hash = block.hash();
var height = new Buffer(4);
height.writeUInt32LE(entry.height, 0, true);
this.put(layout.h(hash), height);
this.put(layout.h(hash), U32(entry.height));
this.put(layout.e(hash), entry.toRaw());
this.cacheHash.set(entry.hash, entry);

View File

@ -1927,6 +1927,18 @@ utils.VerifyResult = function VerifyResult() {
this.score = 0;
};
/**
* Serialize number as a u32le.
* @param {Number} num
* @returns {Buffer}
*/
utils.U32 = function U32(num) {
var data = new Buffer(4);
data.writeUInt32LE(num, 0, true);
return data;
};
/**
* Create a lazy loader.
* @param {Function} require

View File

@ -45,8 +45,11 @@ layout.walletdb = {
return [+key.slice(1, 11), key.slice(11)];
},
R: 'R',
b: function b(hash) {
return 'b' + hash;
b: function b(height) {
return 'b' + pad32(height);
},
bb: function bb(key) {
return +key.slice(1);
},
e: function e(hash) {
return 'e' + hash;

View File

@ -455,23 +455,27 @@ TXDB.prototype.hasPath = function hasPath(output) {
* @returns {Promise}
*/
TXDB.prototype.resolve = co(function* add(tx) {
var hash, result;
TXDB.prototype.resolve = co(function* resolve(tx, block) {
var orphan, hash, result;
if (!this.options.resolution)
return [tx];
if (!this.options.resolution) {
orphan = new ResolvedOrphan(tx, block);
return [orphan];
}
hash = tx.hash('hex');
if (yield this.hasTX(hash))
return [tx];
if (yield this.hasTX(hash)) {
orphan = new ResolvedOrphan(tx, block);
return [orphan];
}
result = yield this.verifyInputs(tx);
result = yield this.verifyInputs(tx, block);
if (!result)
return [];
return yield this.resolveOutputs(tx);
return yield this.resolveOutputs(tx, block);
});
/**
@ -481,7 +485,7 @@ TXDB.prototype.resolve = co(function* add(tx) {
* @returns {Promise}
*/
TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
TXDB.prototype.verifyInputs = co(function* verifyInputs(tx, block) {
var hash = tx.hash('hex');
var hasOrphans = false;
var orphans = [];
@ -573,7 +577,7 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
if (!this.count[hash])
this.count[hash] = 0;
this.orphans[key].push(new Orphan(tx, i));
this.orphans[key].push(new Orphan(tx, i, block));
this.count[hash]++;
this.totalOrphans++;
@ -595,7 +599,7 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
* @returns {Promise}
*/
TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, block, resolved) {
var hash = tx.hash('hex');
var i, j, input, output, key;
var orphans, orphan, coin, valid;
@ -607,7 +611,7 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
// Necessary for the first resolved tx
// as well as the recursive behavior
// below.
resolved.push(tx);
resolved.push(new ResolvedOrphan(tx, block));
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
@ -647,7 +651,7 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
if (valid) {
if (--this.count[orphan.hash] === 0) {
delete this.count[orphan.hash];
yield this.resolveOutputs(orphan.tx, resolved);
yield this.resolveOutputs(orphan.tx, orphan.block, resolved);
}
break;
}
@ -897,8 +901,12 @@ TXDB.prototype._add = co(function* add(tx, block) {
if (existing) {
// Existing tx is already confirmed. Ignore.
if (existing.height !== -1)
if (existing.height !== -1) {
if (block) {
// XXX need to return true here, could be a rescan.
}
return;
}
// The incoming tx won't confirm the
// existing one anyway. Ignore.
@ -909,7 +917,7 @@ TXDB.prototype._add = co(function* add(tx, block) {
tx.ps = existing.ps;
// Confirm transaction.
return yield this.confirm(tx, block);
return yield this._confirm(tx, block);
}
if (tx.height === -1) {
@ -1126,65 +1134,58 @@ TXDB.prototype.addTXRecord = co(function* addTXRecord(tx) {
this.walletdb.writeTX(this.wallet, hash, wids);
});
TXDB.prototype.addBlockRecord = co(function* addBlockRecord(tx, record) {
var hash = tx.hash('hex');
var block = yield this.walletdb.getBlock(record.hash);
if (!block)
block = record.clone();
if (block.hashes.indexOf(hash) !== -1)
return;
block.hashes.push(hash);
this.walletdb.writeBlock(this.wallet, block);
});
TXDB.prototype.removeTXRecord = co(function* removeTXRecord(tx) {
var hash = tx.hash('hex');
var wids = yield this.walletdb.getWalletsByTX(hash);
var result;
if (!wids)
return true;
return;
result = utils.binaryRemove(wids, this.wallet.wid, cmp);
if (!result)
return false;
return;
if (wids.length > 0) {
this.walletdb.writeTX(this.wallet, hash, wids);
return false;
yield this.removeBlockRecord(tx, tx.height);
if (wids.length === 0) {
this.walletdb.unwriteTX(this.wallet, hash);
return;
}
this.walletdb.removeTX(this.wallet, hash);
return true;
this.walletdb.writeTX(this.wallet, hash, wids);
});
TXDB.prototype.removeBlockRecord = co(function* removeBlockRecord(tx, blk) {
TXDB.prototype.addBlockRecord = co(function* addBlockRecord(tx, record) {
var hash = tx.hash('hex');
var block = yield this.walletdb.getBlock(blk);
var index;
var block = yield this.walletdb.getBlock(record.height);
if (!block)
block = record.clone();
if (!block.add(hash, this.wallet.wid))
return;
this.walletdb.writeBlock(this.wallet, block);
});
TXDB.prototype.removeBlockRecord = co(function* removeBlockRecord(tx, height) {
var hash = tx.hash('hex');
var block = yield this.walletdb.getBlock(height);
if (!block)
return;
index = block.hashes.indexOf(hash);
if (index === -1)
if (!block.remove(hash, this.wallet.wid))
return;
block.hashes.splice(index, 1);
if (block.hashes.length > 0) {
this.walletdb.writeBlock(this.wallet, block);
if (block.txs.length === 0) {
this.walletdb.unwriteBlock(this.wallet, block);
return;
}
this.walletdb.removeBlock(this.wallet, block);
this.walletdb.writeBlock(this.wallet, block);
});
@ -1199,13 +1200,48 @@ function cmp(a, b) {
* @returns {Promise}
*/
TXDB.prototype.confirm = co(function* confirm(tx, block) {
TXDB.prototype.confirm = co(function* confirm(hash, block) {
var tx = yield this.getTX(hash);
var details;
if (!tx)
return;
tx.height = block.height;
tx.block = block.hash;
tx.index = -1;
tx.ts = utils.now();
this.start();
try {
details = yield this._confirm(tx, block);
} catch (e) {
this.drop();
throw e;
}
yield this.commit();
return details;
});
/**
* Attempt to confirm a transaction.
* @private
* @param {TX} tx
* @returns {Promise}
*/
TXDB.prototype._confirm = co(function* confirm(tx, block) {
var hash = tx.hash('hex');
var height = block ? block.height : this.walletdb.height;
var details = new Details(this, tx, height);
var i, account, output, coin, input, prevout;
var path, credit, credits;
assert(tx.height !== -1);
if (!tx.isCoinbase()) {
credits = yield this.getSpentCredits(tx);
@ -1427,10 +1463,7 @@ TXDB.prototype.erase = co(function* erase(tx) {
this.del(layout.H(account, tx.height, hash));
}
if (yield this.removeTXRecord(tx)) {
if (tx.block)
yield this.removeBlockRecord(tx, tx.block);
}
yield this.removeTXRecord(tx);
// Update the transaction counter
// and commit new state due to
@ -1601,6 +1634,8 @@ TXDB.prototype.disconnect = co(function* disconnect(tx, block) {
this.saveCredit(credit, path);
}
yield this.removeBlockRecord(tx, block.height);
// We need to update the now-removed
// block properties and reindex due
// to the height change.
@ -3168,10 +3203,16 @@ DetailsMember.prototype.toJSON = function toJSON(network) {
* Helpers
*/
function Orphan(tx, index) {
function Orphan(tx, index, block) {
this.tx = tx;
this.hash = tx.hash('hex');
this.index = index;
this.block = block || null;
}
function ResolvedOrphan(tx, block) {
this.tx = tx;
this.block = block || null;
}
function cmp(a, b) {

View File

@ -1833,13 +1833,14 @@ Wallet.prototype.add = co(function* add(tx, block) {
Wallet.prototype._add = co(function* add(tx, block) {
var resolved = yield this.txdb.resolve(tx, block);
var result = false;
var i;
var i, orphan;
if (resolved.length === 0)
return true;
for (i = 0; i < resolved.length; i++) {
if (yield this._insert(resolved[i], block))
orphan = resolved[i];
if (yield this._insert(orphan.tx, orphan.block))
result = true;
}
@ -1891,6 +1892,21 @@ Wallet.prototype.unconfirm = co(function* unconfirm(hash, block) {
}
});
/**
* Confirm a wallet transcation.
* @param {Hash} hash
* @returns {Promise}
*/
Wallet.prototype.confirm = co(function* confirm(hash, block) {
var unlock = yield this.writeLock.lock();
try {
return yield this.txdb.confirm(hash, block);
} finally {
unlock();
}
});
/**
* Remove a wallet transaction.
* @param {Hash} hash

View File

@ -26,6 +26,7 @@ var Bloom = require('../utils/bloom');
var Logger = require('../node/logger');
var TX = require('../primitives/tx');
var TXDB = require('./txdb');
var U32 = utils.U32;
/*
* Database Layout:
@ -101,12 +102,15 @@ var layout = {
return [key.readUInt32BE(1, true), key.toString('ascii', 5)];
},
R: new Buffer([0x52]),
b: function b(hash) {
var key = new Buffer(33);
b: function b(height) {
var key = new Buffer(5);
key[0] = 0x62;
key.write(hash, 1, 'hex');
key.writeUInt32BE(height, 1, true);
return key;
},
bb: function bb(key) {
return key.readUInt32BE(1, true);
},
e: function e(hash) {
var key = new Buffer(33);
key[0] = 0x65;
@ -287,6 +291,21 @@ WalletDB.prototype.getDepth = co(function* getDepth() {
return depth + 1;
});
/**
* Get current block height.
* @private
* @returns {Promise}
*/
WalletDB.prototype.getHeight = co(function* getHeight() {
var data = yield this.db.get(layout.R);
if (!data)
return -1;
return data.readUInt32LE(0, true);
});
/**
* Start batch.
* @private
@ -538,14 +557,11 @@ WalletDB.prototype.save = function save(wallet) {
var wid = wallet.wid;
var id = wallet.id;
var batch = this.batch(wallet);
var buf = new Buffer(4);
this.widCache.set(id, wid);
batch.put(layout.w(wid), wallet.toRaw());
buf.writeUInt32LE(wid, 0, true);
batch.put(layout.l(id), buf);
batch.put(layout.l(id), U32(wid));
};
/**
@ -788,12 +804,9 @@ WalletDB.prototype.saveAccount = function saveAccount(account) {
var index = account.accountIndex;
var name = account.name;
var batch = this.batch(wallet);
var buf = new Buffer(4);
buf.writeUInt32LE(index, 0, true);
batch.put(layout.a(wid, index), account.toRaw());
batch.put(layout.i(wid, name), buf);
batch.put(layout.i(wid, name), U32(index));
wallet.accountCache.set(index, account);
};
@ -1079,14 +1092,19 @@ WalletDB.prototype._rescan = co(function* rescan(chaindb, height) {
var self = this;
var hashes;
if (height == null)
height = this.height;
if (height == null) {
height = this.height - 36;
if (height < 0)
height = 0;
}
yield this.rollback(height);
hashes = yield this.getHashes();
this.logger.info('Scanning for %d addresses.', hashes.length);
yield chaindb.scan(height, hashes, function(block, txs) {
yield chaindb.scan(this.tip, hashes, function(block, txs) {
return self._addBlock(block, txs);
});
});
@ -1313,7 +1331,8 @@ WalletDB.prototype.writeGenesis = co(function* writeGenesis() {
*/
WalletDB.prototype.getTip = co(function* getTip() {
var data = yield this.db.get(layout.R);
var height = yield this.getHeight();
var data = yield this.db.get(layout.b(height));
if (!data)
return;
@ -1322,16 +1341,32 @@ WalletDB.prototype.getTip = co(function* getTip() {
});
/**
* Write the best block hash.
* Write the tip immediately.
* @param {Hash} hash
* @param {Number} height
* @returns {Promise}
*/
WalletDB.prototype.setTip = co(function* setTip(hash, height) {
WalletDB.prototype.setTip = function setTip(hash, height) {
var block = new WalletBlock(hash, height);
return this.setBlock(block);
};
yield this.db.put(layout.R, block.toTip());
/**
* Write the connecting block immediately.
* @param {Hash} hash
* @param {Number} height
* @returns {Promise}
*/
WalletDB.prototype.setBlock = co(function* setBlock(block) {
var batch = this.db.batch();
batch.del(layout.b(block.height + 1));
batch.put(layout.b(block.height), block.toRaw());
batch.put(layout.R, U32(block.height));
yield batch.write();
this.tip = block.hash;
this.height = block.height;
@ -1345,7 +1380,20 @@ WalletDB.prototype.setTip = co(function* setTip(hash, height) {
WalletDB.prototype.writeBlock = function writeBlock(wallet, block) {
var batch = this.batch(wallet);
batch.put(layout.b(block.hash), block.toRaw());
batch.put(layout.b(block.height), block.toRaw());
batch.put(layout.R, U32(block.height));
};
/**
* Connect a block.
* @param {WalletBlock} block
* @returns {Promise}
*/
WalletDB.prototype.unwriteBlock = function unwriteBlock(wallet, block) {
var batch = this.batch(wallet);
batch.del(layout.b(block.height));
batch.put(layout.R, U32(block.height - 1));
};
/**
@ -1356,19 +1404,8 @@ WalletDB.prototype.writeBlock = function writeBlock(wallet, block) {
WalletDB.prototype.writeTX = function writeTX(wallet, hash, wids) {
var batch = this.batch(wallet);
this.addFilter(hash);
batch.put(layout.e(hash), serializeWallets(wids));
};
/**
* Connect a block.
* @param {WalletBlock} block
* @returns {Promise}
*/
WalletDB.prototype.removeBlock = function removeBlock(wallet, block) {
var batch = this.batch(wallet);
batch.del(layout.b(block.hash));
this.addFilter(hash);
};
/**
@ -1377,7 +1414,7 @@ WalletDB.prototype.removeBlock = function removeBlock(wallet, block) {
* @returns {Promise}
*/
WalletDB.prototype.removeTX = function removeTX(wallet, hash) {
WalletDB.prototype.unwriteTX = function unwriteTX(wallet, hash) {
var batch = this.batch(wallet);
batch.del(layout.e(hash));
};
@ -1388,13 +1425,13 @@ WalletDB.prototype.removeTX = function removeTX(wallet, hash) {
* @returns {Promise}
*/
WalletDB.prototype.getBlock = co(function* getBlock(hash) {
var data = yield this.db.get(layout.b(hash));
WalletDB.prototype.getBlock = co(function* getBlock(height) {
var data = yield this.db.get(layout.b(height));
if (!data)
return;
return WalletBlock.fromRaw(hash, data);
return WalletBlock.fromRaw(data);
});
/**
@ -1412,6 +1449,28 @@ WalletDB.prototype.getWalletsByTX = co(function* getWalletsByTX(hash) {
return parseWallets(data);
});
/**
* Sync with chain height.
* @param {Number} height
* @returns {Promise}
*/
WalletDB.prototype.rollback = co(function* rollback(height) {
var block;
if (this.height > height) {
this.logger.info(
'Rolling back %d blocks to height %d.',
this.height - height, height);
}
while (this.height > height) {
block = yield this.getBlock(this.height);
assert(block);
yield this._removeBlock(block);
}
});
/**
* Add a block's transactions and write the new best hash.
* @param {ChainEntry} entry
@ -1421,6 +1480,7 @@ WalletDB.prototype.getWalletsByTX = co(function* getWalletsByTX(hash) {
WalletDB.prototype.addBlock = co(function* addBlock(entry, txs) {
var unlock = yield this.txLock.lock();
try {
yield this.rollback(entry.height - 1);
return yield this._addBlock(entry, txs);
} finally {
unlock();
@ -1440,7 +1500,8 @@ WalletDB.prototype._addBlock = co(function* addBlock(entry, txs) {
if (this.options.useCheckpoints) {
if (entry.height <= this.network.checkpoints.lastHeight) {
yield this.setTip(entry.hash, entry.height);
block = WalletBlock.fromEntry(entry);
yield this.setBlock(block);
return;
}
}
@ -1456,12 +1517,18 @@ WalletDB.prototype._addBlock = co(function* addBlock(entry, txs) {
total++;
}
if (total > 0) {
this.logger.info('Connecting block %s (%d txs).',
utils.revHex(block.hash), total);
if (total === 0) {
yield this.setBlock(block);
return total;
}
yield this.setTip(entry.hash, entry.height);
this.height = block.height;
this.tip = block.hash;
this.logger.info('Connected block %s (tx=%d).',
utils.revHex(block.hash), total);
return total;
});
/**
@ -1474,6 +1541,7 @@ WalletDB.prototype._addBlock = co(function* addBlock(entry, txs) {
WalletDB.prototype.removeBlock = co(function* removeBlock(entry) {
var unlock = yield this.txLock.lock();
try {
yield this.rollback(entry.height);
return yield this._removeBlock(entry);
} finally {
unlock();
@ -1488,24 +1556,34 @@ WalletDB.prototype.removeBlock = co(function* removeBlock(entry) {
*/
WalletDB.prototype._removeBlock = co(function* removeBlock(entry) {
var block = WalletBlock.fromEntry(entry);
var data = yield this.getBlock(block.hash);
var i, hash;
var block = yield this.getBlock(entry.height);
var i, tx, prev;
if (data)
block.hashes = data.hashes;
if (!block)
return;
if (block.hashes.length > 0) {
this.logger.warning('Disconnecting block %s (%d txs).',
utils.revHex(block.hash), block.hashes.length);
assert(block.height > 0);
prev = yield this.getBlock(entry.height - 1);
assert(prev);
if (block.txs.length === 0) {
yield this.setBlock(prev);
return block.txs.length;
}
for (i = block.hashes.length - 1; i >= 0; i--) {
hash = block.hashes[i];
yield this._unconfirmTX(hash, block);
for (i = block.txs.length - 1; i >= 0; i--) {
tx = block.txs[i];
yield this._unconfirmTX(tx, block);
}
yield this.setTip(block.prevBlock, block.height - 1);
this.height = prev.height;
this.tip = prev.hash;
this.logger.warning('Disconnected block %s (tx=%d).',
utils.revHex(block.hash), block.txs.length);
return block.txs.length;
});
/**
@ -1568,17 +1646,21 @@ WalletDB.prototype._insertTX = co(function* insertTX(tx, block) {
});
/**
* Unconfirm a transaction from all relevant wallets.
* @param {Hash} hash
* Confirm a transaction from all
* relevant wallets without a lock.
* @private
* @param {TXHash} hash
* @returns {Promise}
*/
WalletDB.prototype.unconfirmTX = co(function* unconfirmTX(hash, block) {
var unlock = yield this.txLock.lock();
try {
return yield this._unconfirmTX(hash, block);
} finally {
unlock();
WalletDB.prototype._confirmTX = co(function* confirmTX(tx, block) {
var i, wid, wallet;
for (i = 0; i < tx.wids.length; i++) {
wid = tx.wids[i];
wallet = yield this.get(wid);
assert(wallet);
yield wallet.confirm(tx.hash, block);
}
});
@ -1586,22 +1668,18 @@ WalletDB.prototype.unconfirmTX = co(function* unconfirmTX(hash, block) {
* Unconfirm a transaction from all
* relevant wallets without a lock.
* @private
* @param {Hash} hash
* @param {TXHash} hash
* @returns {Promise}
*/
WalletDB.prototype._unconfirmTX = co(function* unconfirmTX(hash, block) {
var wids = yield this.getWalletsByTX(hash);
WalletDB.prototype._unconfirmTX = co(function* unconfirmTX(tx, block) {
var i, wid, wallet;
if (!wids)
return;
for (i = 0; i < wids.length; i++) {
wid = wids[i];
for (i = 0; i < tx.wids.length; i++) {
wid = tx.wids[i];
wallet = yield this.get(wid);
assert(wallet);
yield wallet.unconfirm(hash, block);
yield wallet.unconfirm(tx.hash, block);
}
});
@ -1652,8 +1730,8 @@ function WalletBlock(hash, height) {
this.hash = hash || constants.NULL_HASH;
this.height = height != null ? height : -1;
this.prevBlock = constants.NULL_HASH;
this.hashes = [];
this.txs = [];
this.index = {};
}
/**
@ -1662,10 +1740,7 @@ function WalletBlock(hash, height) {
*/
WalletBlock.prototype.clone = function clone() {
var block = new WalletBlock(this.hash, this.height);
block.prevBlock = this.prevBlock;
block.hashes = this.hashes.slice();
return block;
return new WalletBlock(this.hash, this.height);
};
/**
@ -1677,7 +1752,6 @@ WalletBlock.prototype.clone = function clone() {
WalletBlock.prototype.fromEntry = function fromEntry(entry) {
this.hash = entry.hash;
this.height = entry.height;
this.prevBlock = entry.prevBlock;
return this;
};
@ -1690,8 +1764,6 @@ WalletBlock.prototype.fromEntry = function fromEntry(entry) {
WalletBlock.prototype.fromJSON = function fromJSON(json) {
this.hash = utils.revHex(json.hash);
this.height = json.height;
if (json.prevBlock)
this.prevBlock = utils.revHex(json.prevBlock);
return this;
};
@ -1702,12 +1774,23 @@ WalletBlock.prototype.fromJSON = function fromJSON(json) {
* @param {Buffer} data
*/
WalletBlock.prototype.fromRaw = function fromRaw(hash, data) {
WalletBlock.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
this.hash = hash;
var i, hash, tx, count;
this.hash = p.readHash('hex');
this.height = p.readU32();
while (p.left())
this.hashes.push(p.readHash('hex'));
while (p.left()) {
hash = p.readHash('hex');
tx = new TXHash(hash);
count = p.readVarint();
for (i = 0; i < count; i++)
tx.wids.push(p.readU32());
this.txs.push(tx);
this.index[tx.hash] = tx;
}
return this;
};
@ -1751,8 +1834,8 @@ WalletBlock.fromJSON = function fromJSON(json) {
* @returns {WalletBlock}
*/
WalletBlock.fromRaw = function fromRaw(hash, data) {
return new WalletBlock().fromRaw(hash, data);
WalletBlock.fromRaw = function fromRaw(data) {
return new WalletBlock().fromRaw(data);
};
/**
@ -1790,12 +1873,18 @@ WalletBlock.prototype.toTip = function toTip(writer) {
WalletBlock.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
var i;
var i, j, tx;
p.writeHash(this.hash);
p.writeU32(this.height);
for (i = 0; i < this.hashes.length; i++)
p.writeHash(this.hashes[i]);
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
p.writeHash(tx.hash);
p.writeVarint(tx.wids.length);
for (j = 0; j < tx.wids.length; j++)
p.writeU32(tx.wids[j]);
}
if (!writer)
p = p.render();
@ -1803,6 +1892,61 @@ WalletBlock.prototype.toRaw = function toRaw(writer) {
return p;
};
/**
* Add a hash and wid pair to the block.
* @param {Hash} hash
* @param {WalletID} wid
* @returns {Boolean}
*/
WalletBlock.prototype.add = function add(hash, wid) {
var tx = this.index[hash];
var index;
if (!tx) {
tx = new TXHash(hash);
tx.wids.push(wid);
this.txs.push(tx);
this.index[tx.hash] = tx;
return true;
}
index = utils.binaryInsert(tx.wids, wid, cmp, true);
if (index === -1)
return false;
return true;
};
/**
* Remove a hash and wid pair from the block.
* @param {Hash} hash
* @param {WalletID} wid
* @returns {Boolean}
*/
WalletBlock.prototype.remove = function remove(hash, wid) {
var tx = this.index[hash];
var result;
if (!tx)
return false;
result = utils.binaryRemove(tx.wids, wid, cmp);
if (!result)
return false;
if (tx.wids.length === 0) {
result = utils.binaryRemove(this.txs, tx, cmpid);
assert(result);
delete this.index[tx.hash];
}
return true;
};
/**
* Convert the block to a more json-friendly object.
* @returns {Object}
@ -1819,6 +1963,14 @@ WalletBlock.prototype.toJSON = function toJSON() {
* Helpers
*/
function TXHash(hash, wids) {
this.hash = hash || constants.NULL_HASH;
this.wids = wids || [];
this.id = TXHash.id++;
}
TXHash.id = 0;
function parseWallets(data) {
var p = new BufferReader(data);
var wids = [];
@ -1845,6 +1997,10 @@ function cmp(a, b) {
return a - b;
}
function cmpid(a, b) {
return a.id - b.id;
}
/*
* Expose
*/