walletdb: store chain by height.
This commit is contained in:
parent
282a8f7bb4
commit
293bf20b9c
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user