wallet: refactor mappings to use maps and sets.

This commit is contained in:
Christopher Jeffrey 2017-07-25 13:39:38 -07:00
parent 02e4dda012
commit 3f9d57f680
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 82 additions and 73 deletions

View File

@ -237,8 +237,7 @@ function BlockMapRecord(height) {
return new BlockMapRecord(height); return new BlockMapRecord(height);
this.height = height != null ? height : -1; this.height = height != null ? height : -1;
this.txs = []; this.txs = new Map();
this.index = new Map();
} }
/** /**
@ -255,8 +254,7 @@ BlockMapRecord.prototype.fromRaw = function fromRaw(data) {
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
let hash = br.readHash('hex'); let hash = br.readHash('hex');
let tx = TXMapRecord.fromReader(hash, br); let tx = TXMapRecord.fromReader(hash, br);
this.txs.push(tx); this.txs.set(tx.hash, tx);
this.index.set(tx.hash, tx);
} }
return this; return this;
@ -283,7 +281,7 @@ BlockMapRecord.prototype.getSize = function getSize() {
size += 4; size += 4;
for (let tx of this.txs) { for (let tx of this.txs.values()) {
size += 32; size += 32;
size += tx.getSize(); size += tx.getSize();
} }
@ -301,10 +299,10 @@ BlockMapRecord.prototype.toRaw = function toRaw() {
let size = this.getSize(); let size = this.getSize();
let bw = new StaticWriter(size); let bw = new StaticWriter(size);
bw.writeU32(this.txs.length); bw.writeU32(this.txs.size);
for (let tx of this.txs) { for (let [hash, tx] of this.txs) {
bw.writeHash(tx.hash); bw.writeHash(hash);
tx.toWriter(bw); tx.toWriter(bw);
} }
@ -319,14 +317,11 @@ BlockMapRecord.prototype.toRaw = function toRaw() {
*/ */
BlockMapRecord.prototype.add = function add(hash, wid) { BlockMapRecord.prototype.add = function add(hash, wid) {
let tx = this.index.get(hash); let tx = this.txs.get(hash);
if (!tx) { if (!tx) {
tx = new TXMapRecord(hash); tx = new TXMapRecord(hash);
tx.wids.push(wid); this.txs.set(tx.hash, tx);
this.txs.push(tx);
this.index.set(tx.hash, tx);
return true;
} }
return tx.add(wid); return tx.add(wid);
@ -340,7 +335,7 @@ BlockMapRecord.prototype.add = function add(hash, wid) {
*/ */
BlockMapRecord.prototype.remove = function remove(hash, wid) { BlockMapRecord.prototype.remove = function remove(hash, wid) {
let tx = this.index.get(hash); let tx = this.txs.get(hash);
if (!tx) if (!tx)
return false; return false;
@ -348,15 +343,26 @@ BlockMapRecord.prototype.remove = function remove(hash, wid) {
if (!tx.remove(wid)) if (!tx.remove(wid))
return false; return false;
if (tx.wids.length === 0) { if (tx.wids.size === 0)
let result = util.binaryRemove(this.txs, tx, cmpid); this.txs.delete(tx.hash);
assert(result);
this.index.delete(tx.hash);
}
return true; return true;
}; };
/**
* Convert tx map to an array.
* @returns {Array}
*/
BlockMapRecord.prototype.toArray = function toArray() {
let txs = [];
for (let tx of this.txs.values())
txs.push(tx);
return txs;
};
/** /**
* TX Hash * TX Hash
* @constructor * @constructor
@ -364,18 +370,19 @@ BlockMapRecord.prototype.remove = function remove(hash, wid) {
function TXMapRecord(hash, wids) { function TXMapRecord(hash, wids) {
this.hash = hash || encoding.NULL_HASH; this.hash = hash || encoding.NULL_HASH;
this.wids = wids || []; this.wids = wids || new Set();
this.id = TXMapRecord.id++;
} }
TXMapRecord.id = 0;
TXMapRecord.prototype.add = function add(wid) { TXMapRecord.prototype.add = function add(wid) {
return util.binaryInsert(this.wids, wid, cmp, true) !== -1; if (this.wids.has(wid))
return false;
this.wids.add(wid);
return true;
}; };
TXMapRecord.prototype.remove = function remove(wid) { TXMapRecord.prototype.remove = function remove(wid) {
return util.binaryRemove(this.wids, wid, cmp); return this.wids.delete(wid);
}; };
TXMapRecord.prototype.toWriter = function toWriter(bw) { TXMapRecord.prototype.toWriter = function toWriter(bw) {
@ -416,15 +423,19 @@ TXMapRecord.fromRaw = function fromRaw(hash, data) {
function OutpointMapRecord(hash, index, wids) { function OutpointMapRecord(hash, index, wids) {
this.hash = hash || encoding.NULL_HASH; this.hash = hash || encoding.NULL_HASH;
this.index = index != null ? index : -1; this.index = index != null ? index : -1;
this.wids = wids || []; this.wids = wids || new Set();
} }
OutpointMapRecord.prototype.add = function add(wid) { OutpointMapRecord.prototype.add = function add(wid) {
return util.binaryInsert(this.wids, wid, cmp, true) !== -1; if (this.wids.has(wid))
return false;
this.wids.add(wid);
return true;
}; };
OutpointMapRecord.prototype.remove = function remove(wid) { OutpointMapRecord.prototype.remove = function remove(wid) {
return util.binaryRemove(this.wids, wid, cmp); return this.wids.delete(wid);
}; };
OutpointMapRecord.prototype.toWriter = function toWriter(bw) { OutpointMapRecord.prototype.toWriter = function toWriter(bw) {
@ -464,15 +475,19 @@ OutpointMapRecord.fromRaw = function fromRaw(hash, index, data) {
function PathMapRecord(hash, wids) { function PathMapRecord(hash, wids) {
this.hash = hash || encoding.NULL_HASH; this.hash = hash || encoding.NULL_HASH;
this.wids = wids || []; this.wids = wids || new Set();
} }
PathMapRecord.prototype.add = function add(wid) { PathMapRecord.prototype.add = function add(wid) {
return util.binaryInsert(this.wids, wid, cmp, true) !== -1; if (this.wids.has(wid))
return false;
this.wids.add(wid);
return true;
}; };
PathMapRecord.prototype.remove = function remove(wid) { PathMapRecord.prototype.remove = function remove(wid) {
return util.binaryRemove(this.wids, wid, cmp); return this.wids.delete(wid);
}; };
PathMapRecord.prototype.toWriter = function toWriter(bw) { PathMapRecord.prototype.toWriter = function toWriter(bw) {
@ -701,30 +716,22 @@ TXRecord.fromRaw = function fromRaw(data) {
* Helpers * Helpers
*/ */
function cmp(a, b) {
return a - b;
}
function cmpid(a, b) {
return a.id - b.id;
}
function parseWallets(br) { function parseWallets(br) {
let count = br.readU32(); let count = br.readU32();
let wids = []; let wids = new Set();
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)
wids.push(br.readU32()); wids.add(br.readU32());
return wids; return wids;
} }
function sizeWallets(wids) { function sizeWallets(wids) {
return 4 + wids.length * 4; return 4 + wids.size * 4;
} }
function serializeWallets(bw, wids) { function serializeWallets(bw, wids) {
bw.writeU32(wids.length); bw.writeU32(wids.size);
for (let wid of wids) for (let wid of wids)
bw.writeU32(wid); bw.writeU32(wid);

View File

@ -520,7 +520,7 @@ TXDB.prototype.removeOutpointMap = async function removeOutpointMap(hash, i) {
if (!map.remove(this.wallet.wid)) if (!map.remove(this.wallet.wid))
return; return;
if (map.wids.length === 0) { if (map.wids.size === 0) {
this.walletdb.unwriteOutpointMap(this.wallet, hash, i); this.walletdb.unwriteOutpointMap(this.wallet, hash, i);
return; return;
} }
@ -563,7 +563,7 @@ TXDB.prototype.removeBlockMap = async function removeBlockMap(hash, height) {
if (!block.remove(hash, this.wallet.wid)) if (!block.remove(hash, this.wallet.wid))
return; return;
if (block.txs.length === 0) { if (block.txs.size === 0) {
this.walletdb.unwriteBlockMap(this.wallet, height); this.walletdb.unwriteBlockMap(this.wallet, height);
return; return;
} }
@ -795,6 +795,7 @@ TXDB.prototype.insert = async function insert(wtx, block) {
let hash = wtx.hash; let hash = wtx.hash;
let height = block ? block.height : -1; let height = block ? block.height : -1;
let details = new Details(this, wtx, block); let details = new Details(this, wtx, block);
let accounts = new Set();
let own = false; let own = false;
let updated = false; let updated = false;
@ -838,6 +839,7 @@ TXDB.prototype.insert = async function insert(wtx, block) {
// Build the tx details object // Build the tx details object
// as we go, for speed. // as we go, for speed.
details.setInput(i, path, coin); details.setInput(i, path, coin);
accounts.add(path.account);
// Write an undo coin for the credit // Write an undo coin for the credit
// and add it to the stxo set. // and add it to the stxo set.
@ -886,6 +888,7 @@ TXDB.prototype.insert = async function insert(wtx, block) {
continue; continue;
details.setOutput(i, path); details.setOutput(i, path);
accounts.add(path.account);
// Attempt to resolve an input we // Attempt to resolve an input we
// did not know was ours at the time. // did not know was ours at the time.
@ -928,7 +931,7 @@ TXDB.prototype.insert = async function insert(wtx, block) {
// Do some secondary indexing for account-based // Do some secondary indexing for account-based
// queries. This saves us a lot of time for // queries. This saves us a lot of time for
// queries later. // queries later.
for (let account of details.accounts) { for (let account of accounts) {
this.put(layout.T(account, hash), null); this.put(layout.T(account, hash), null);
this.put(layout.M(account, wtx.ps, hash), null); this.put(layout.M(account, wtx.ps, hash), null);
@ -1013,6 +1016,7 @@ TXDB.prototype._confirm = async function confirm(wtx, block) {
let hash = wtx.hash; let hash = wtx.hash;
let height = block.height; let height = block.height;
let details = new Details(this, wtx, block); let details = new Details(this, wtx, block);
let accounts = new Set();
wtx.setBlock(block); wtx.setBlock(block);
@ -1055,6 +1059,7 @@ TXDB.prototype._confirm = async function confirm(wtx, block) {
assert(path); assert(path);
details.setInput(i, path, coin); details.setInput(i, path, coin);
accounts.add(path.account);
// We can now safely remove the credit // We can now safely remove the credit
// entirely, now that we know it's also // entirely, now that we know it's also
@ -1075,6 +1080,7 @@ TXDB.prototype._confirm = async function confirm(wtx, block) {
continue; continue;
details.setOutput(i, path); details.setOutput(i, path);
accounts.add(path.account);
credit = await this.getCredit(hash, i); credit = await this.getCredit(hash, i);
assert(credit); assert(credit);
@ -1107,7 +1113,7 @@ TXDB.prototype._confirm = async function confirm(wtx, block) {
this.put(layout.h(height, hash), null); this.put(layout.h(height, hash), null);
// Secondary indexing also needs to change. // Secondary indexing also needs to change.
for (let account of details.accounts) { for (let account of accounts) {
this.del(layout.P(account, hash)); this.del(layout.P(account, hash));
this.put(layout.H(account, height, hash), null); this.put(layout.H(account, height, hash), null);
} }
@ -1157,6 +1163,7 @@ TXDB.prototype.erase = async function erase(wtx, block) {
let hash = wtx.hash; let hash = wtx.hash;
let height = block ? block.height : -1; let height = block ? block.height : -1;
let details = new Details(this, wtx, block); let details = new Details(this, wtx, block);
let accounts = new Set();
if (!tx.isCoinbase()) { if (!tx.isCoinbase()) {
// We need to undo every part of the // We need to undo every part of the
@ -1181,6 +1188,7 @@ TXDB.prototype.erase = async function erase(wtx, block) {
assert(path); assert(path);
details.setInput(i, path, coin); details.setInput(i, path, coin);
accounts.add(path.account);
// Recalculate the balance, remove // Recalculate the balance, remove
// from stxo set, remove the undo // from stxo set, remove the undo
@ -1207,6 +1215,7 @@ TXDB.prototype.erase = async function erase(wtx, block) {
continue; continue;
details.setOutput(i, path); details.setOutput(i, path);
accounts.add(path.account);
credit = Credit.fromTX(tx, i, height); credit = Credit.fromTX(tx, i, height);
@ -1233,7 +1242,7 @@ TXDB.prototype.erase = async function erase(wtx, block) {
this.del(layout.h(height, hash)); this.del(layout.h(height, hash));
// Remove all secondary indexing. // Remove all secondary indexing.
for (let account of details.accounts) { for (let account of accounts) {
this.del(layout.T(account, hash)); this.del(layout.T(account, hash));
this.del(layout.M(account, wtx.ps, hash)); this.del(layout.M(account, wtx.ps, hash));
@ -1354,6 +1363,7 @@ TXDB.prototype.disconnect = async function disconnect(wtx, block) {
let hash = wtx.hash; let hash = wtx.hash;
let height = block.height; let height = block.height;
let details = new Details(this, wtx, block); let details = new Details(this, wtx, block);
let accounts = new Set();
assert(block); assert(block);
@ -1380,6 +1390,7 @@ TXDB.prototype.disconnect = async function disconnect(wtx, block) {
assert(path); assert(path);
details.setInput(i, path, coin); details.setInput(i, path, coin);
accounts.add(path.account);
this.pending.confirmed += coin.value; this.pending.confirmed += coin.value;
@ -1412,6 +1423,7 @@ TXDB.prototype.disconnect = async function disconnect(wtx, block) {
await this.updateSpentCoin(tx, i, height); await this.updateSpentCoin(tx, i, height);
details.setOutput(i, path); details.setOutput(i, path);
accounts.add(path.account);
// Update coin height and confirmed // Update coin height and confirmed
// balance. Save once again. // balance. Save once again.
@ -1434,7 +1446,7 @@ TXDB.prototype.disconnect = async function disconnect(wtx, block) {
this.del(layout.h(height, hash)); this.del(layout.h(height, hash));
// Secondary indexing also needs to change. // Secondary indexing also needs to change.
for (let account of details.accounts) { for (let account of accounts) {
this.put(layout.P(account, hash), null); this.put(layout.P(account, hash), null);
this.del(layout.H(account, height, hash)); this.del(layout.H(account, height, hash));
} }
@ -2856,7 +2868,6 @@ function Details(txdb, wtx, block) {
this.inputs = []; this.inputs = [];
this.outputs = []; this.outputs = [];
this.accounts = [];
this.init(); this.init();
} }
@ -2896,10 +2907,8 @@ Details.prototype.setInput = function setInput(i, path, coin) {
member.address = coin.getAddress(); member.address = coin.getAddress();
} }
if (path) { if (path)
member.path = path; member.path = path;
util.binaryInsert(this.accounts, path.account, cmp, true);
}
}; };
/** /**
@ -2911,10 +2920,8 @@ Details.prototype.setInput = function setInput(i, path, coin) {
Details.prototype.setOutput = function setOutput(i, path) { Details.prototype.setOutput = function setOutput(i, path) {
let member = this.outputs[i]; let member = this.outputs[i];
if (path) { if (path)
member.path = path; member.path = path;
util.binaryInsert(this.accounts, path.account, cmp, true);
}
}; };
/** /**
@ -3220,14 +3227,6 @@ BlockRecord.fromMeta = function fromMeta(block) {
return new BlockRecord().fromMeta(block); return new BlockRecord().fromMeta(block);
}; };
/*
* Helpers
*/
function cmp(a, b) {
return a - b;
}
/* /*
* Expose * Expose
*/ */

View File

@ -1880,11 +1880,12 @@ WalletDB.prototype.revert = async function revert(target) {
try { try {
let height = layout.bb(item.key); let height = layout.bb(item.key);
let block = BlockMapRecord.fromRaw(height, item.value); let block = BlockMapRecord.fromRaw(height, item.value);
let txs = block.toArray();
total += block.txs.length; total += txs.length;
for (let i = block.txs.length - 1; i >= 0; i--) { for (let i = txs.length - 1; i >= 0; i--) {
let tx = block.txs[i]; let tx = txs[i];
await this._unconfirm(tx); await this._unconfirm(tx);
} }
} catch (e) { } catch (e) {
@ -1988,7 +1989,7 @@ WalletDB.prototype.removeBlock = async function removeBlock(entry) {
WalletDB.prototype._removeBlock = async function removeBlock(entry) { WalletDB.prototype._removeBlock = async function removeBlock(entry) {
let tip = BlockMeta.fromEntry(entry); let tip = BlockMeta.fromEntry(entry);
let prev, block; let prev, block, txs;
if (tip.height > this.state.height) { if (tip.height > this.state.height) {
this.logger.warning( this.logger.warning(
@ -2013,8 +2014,10 @@ WalletDB.prototype._removeBlock = async function removeBlock(entry) {
return 0; return 0;
} }
for (let i = block.txs.length - 1; i >= 0; i--) { txs = block.toArray();
let tx = block.txs[i];
for (let i = txs.length - 1; i >= 0; i--) {
let tx = txs[i];
await this._unconfirm(tx); await this._unconfirm(tx);
} }
@ -2022,9 +2025,9 @@ WalletDB.prototype._removeBlock = async function removeBlock(entry) {
await this.syncState(prev); await this.syncState(prev);
this.logger.warning('Disconnected wallet block %s (tx=%d).', this.logger.warning('Disconnected wallet block %s (tx=%d).',
util.revHex(tip.hash), block.txs.length); util.revHex(tip.hash), block.txs.size);
return block.txs.length; return block.txs.size;
}; };
/** /**
@ -2086,7 +2089,7 @@ WalletDB.prototype._insert = async function insert(tx, block) {
this.logger.info( this.logger.info(
'Incoming transaction for %d wallets in WalletDB (%s).', 'Incoming transaction for %d wallets in WalletDB (%s).',
wids.length, tx.txid()); wids.size, tx.txid());
// If this is our first transaction // If this is our first transaction
// in a block, set the start block here. // in a block, set the start block here.