|
|
|
|
@ -42,11 +42,7 @@ function TXDB(wdb) {
|
|
|
|
|
this.id = null;
|
|
|
|
|
this.prefix = layout.prefix(0);
|
|
|
|
|
this.wallet = null;
|
|
|
|
|
|
|
|
|
|
this.locked = new Set();
|
|
|
|
|
this.state = null;
|
|
|
|
|
this.pending = null;
|
|
|
|
|
this.events = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -63,29 +59,10 @@ TXDB.layout = layout;
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.open = async function open(wallet) {
|
|
|
|
|
const {wid, id} = wallet;
|
|
|
|
|
|
|
|
|
|
this.id = id;
|
|
|
|
|
this.wid = wid;
|
|
|
|
|
this.prefix = layout.prefix(wid);
|
|
|
|
|
this.wallet = wallet;
|
|
|
|
|
|
|
|
|
|
const state = await this.getState();
|
|
|
|
|
|
|
|
|
|
if (state) {
|
|
|
|
|
this.state = state;
|
|
|
|
|
this.logger.info('TXDB loaded for %s.', id);
|
|
|
|
|
} else {
|
|
|
|
|
this.state = new TXDBState(wid, id);
|
|
|
|
|
this.logger.info('TXDB created for %s.', id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.logger.info('TXDB State: tx=%d coin=%s.',
|
|
|
|
|
this.state.tx, this.state.coin);
|
|
|
|
|
|
|
|
|
|
this.logger.info(
|
|
|
|
|
'Balance: unconfirmed=%s confirmed=%s.',
|
|
|
|
|
Amount.btc(this.state.unconfirmed),
|
|
|
|
|
Amount.btc(this.state.confirmed));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -270,6 +247,31 @@ TXDB.prototype.removeInput = async function removeInput(b, tx, index) {
|
|
|
|
|
return this.removeOutpointMap(b, prevout.hash, prevout.index);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update wallet balance.
|
|
|
|
|
* @param {BalanceDelta} state
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.updateBalance = async function updateBalance(b, state) {
|
|
|
|
|
const balance = await this.getWalletBalance();
|
|
|
|
|
state.applyTo(balance);
|
|
|
|
|
b.put(layout.R, balance.toRaw());
|
|
|
|
|
return balance;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update account balance.
|
|
|
|
|
* @param {Number} acct
|
|
|
|
|
* @param {Balance} delta
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.updateAccountBalance = async function updateAccountBalance(b, acct, delta) {
|
|
|
|
|
const balance = await this.getAccountBalance(acct);
|
|
|
|
|
delta.applyTo(balance);
|
|
|
|
|
b.put(layout.r(acct), balance.toRaw());
|
|
|
|
|
return balance;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test a whether a coin has been spent.
|
|
|
|
|
* @param {Hash} hash
|
|
|
|
|
@ -493,7 +495,7 @@ TXDB.prototype.add = async function add(tx, block) {
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
// Confirm transaction.
|
|
|
|
|
return await this.confirm(existing, block);
|
|
|
|
|
return this.confirm(existing, block);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const wtx = TXRecord.fromTX(tx, block);
|
|
|
|
|
@ -509,7 +511,7 @@ TXDB.prototype.add = async function add(tx, block) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Finally we can do a regular insertion.
|
|
|
|
|
return await this.insert(wtx, block);
|
|
|
|
|
return this.insert(wtx, block);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -522,14 +524,12 @@ TXDB.prototype.add = async function add(tx, block) {
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.insert = async function insert(wtx, block) {
|
|
|
|
|
const b = this.bucket();
|
|
|
|
|
const state = this.state.clone();
|
|
|
|
|
const {tx, hash} = wtx;
|
|
|
|
|
const height = block ? block.height : -1;
|
|
|
|
|
const details = new Details(this, wtx, block);
|
|
|
|
|
const accounts = new Set();
|
|
|
|
|
const state = new BalanceDelta();
|
|
|
|
|
|
|
|
|
|
let own = false;
|
|
|
|
|
let updated = false;
|
|
|
|
|
|
|
|
|
|
if (!tx.isCoinbase()) {
|
|
|
|
|
// We need to potentially spend some coins here.
|
|
|
|
|
@ -553,7 +553,6 @@ TXDB.prototype.insert = async function insert(wtx, block) {
|
|
|
|
|
// Build the tx details object
|
|
|
|
|
// as we go, for speed.
|
|
|
|
|
details.setInput(i, path, coin);
|
|
|
|
|
accounts.add(path.account);
|
|
|
|
|
|
|
|
|
|
// Write an undo coin for the credit
|
|
|
|
|
// and add it to the stxo set.
|
|
|
|
|
@ -563,8 +562,9 @@ TXDB.prototype.insert = async function insert(wtx, block) {
|
|
|
|
|
// be updated as it reflects the on-chain
|
|
|
|
|
// balance _and_ mempool balance assuming
|
|
|
|
|
// everything in the mempool were to confirm.
|
|
|
|
|
state.coin -= 1;
|
|
|
|
|
state.unconfirmed -= coin.value;
|
|
|
|
|
state.tx(path, 1);
|
|
|
|
|
state.coin(path, -1);
|
|
|
|
|
state.unconfirmed(path, -coin.value);
|
|
|
|
|
|
|
|
|
|
if (!block) {
|
|
|
|
|
// If the tx is not mined, we do not
|
|
|
|
|
@ -583,11 +583,10 @@ TXDB.prototype.insert = async function insert(wtx, block) {
|
|
|
|
|
// coin will be indexed as an undo
|
|
|
|
|
// coin so it can be reconnected
|
|
|
|
|
// later during a reorg.
|
|
|
|
|
state.confirmed -= coin.value;
|
|
|
|
|
state.confirmed(path, -coin.value);
|
|
|
|
|
await this.removeCredit(b, credit, path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updated = true;
|
|
|
|
|
own = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -601,25 +600,23 @@ TXDB.prototype.insert = async function insert(wtx, block) {
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
details.setOutput(i, path);
|
|
|
|
|
accounts.add(path.account);
|
|
|
|
|
|
|
|
|
|
const credit = Credit.fromTX(tx, i, height);
|
|
|
|
|
credit.own = own;
|
|
|
|
|
|
|
|
|
|
state.coin += 1;
|
|
|
|
|
state.unconfirmed += output.value;
|
|
|
|
|
state.tx(path, 1);
|
|
|
|
|
state.coin(path, 1);
|
|
|
|
|
state.unconfirmed(path, output.value);
|
|
|
|
|
|
|
|
|
|
if (block)
|
|
|
|
|
state.confirmed += output.value;
|
|
|
|
|
state.confirmed(path, output.value);
|
|
|
|
|
|
|
|
|
|
await this.saveCredit(b, credit, path);
|
|
|
|
|
|
|
|
|
|
updated = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this didn't update any coins,
|
|
|
|
|
// it's not our transaction.
|
|
|
|
|
if (!updated)
|
|
|
|
|
if (!state.updated())
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
// Save and index the transaction record.
|
|
|
|
|
@ -634,46 +631,41 @@ TXDB.prototype.insert = async function insert(wtx, block) {
|
|
|
|
|
// Do some secondary indexing for account-based
|
|
|
|
|
// queries. This saves us a lot of time for
|
|
|
|
|
// queries later.
|
|
|
|
|
for (const account of accounts) {
|
|
|
|
|
b.put(layout.T(account, hash), null);
|
|
|
|
|
b.put(layout.M(account, wtx.mtime, hash), null);
|
|
|
|
|
for (const [acct, delta] of state.accounts) {
|
|
|
|
|
await this.updateAccountBalance(b, acct, delta);
|
|
|
|
|
|
|
|
|
|
b.put(layout.T(acct, hash), null);
|
|
|
|
|
b.put(layout.M(acct, wtx.mtime, hash), null);
|
|
|
|
|
|
|
|
|
|
if (!block)
|
|
|
|
|
b.put(layout.P(account, hash), null);
|
|
|
|
|
b.put(layout.P(acct, hash), null);
|
|
|
|
|
else
|
|
|
|
|
b.put(layout.H(account, height, hash), null);
|
|
|
|
|
b.put(layout.H(acct, height, hash), null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.addTXMap(b, hash);
|
|
|
|
|
|
|
|
|
|
// Update block records.
|
|
|
|
|
if (block) {
|
|
|
|
|
await this.addBlockMap(b, height);
|
|
|
|
|
await this.addBlock(b, tx.hash(), block);
|
|
|
|
|
} else {
|
|
|
|
|
await this.addTXMap(b, hash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the transaction counter and
|
|
|
|
|
// commit the new state. This state will
|
|
|
|
|
// only overwrite the best state once
|
|
|
|
|
// the batch has actually been written
|
|
|
|
|
// to disk.
|
|
|
|
|
state.tx += 1;
|
|
|
|
|
b.put(layout.R, state.commit());
|
|
|
|
|
// Commit the new state.
|
|
|
|
|
const balance = await this.updateBalance(b, state);
|
|
|
|
|
|
|
|
|
|
await b.write();
|
|
|
|
|
|
|
|
|
|
// This transaction may unlock some
|
|
|
|
|
// coins now that we've seen it.
|
|
|
|
|
this.unlockTX(tx);
|
|
|
|
|
|
|
|
|
|
await b.write();
|
|
|
|
|
|
|
|
|
|
this.state = state;
|
|
|
|
|
|
|
|
|
|
// Emit events for potential local and
|
|
|
|
|
// websocket listeners. Note that these
|
|
|
|
|
// will only be emitted if the batch is
|
|
|
|
|
// successfully written to disk.
|
|
|
|
|
this.emit('tx', tx, details);
|
|
|
|
|
this.emit('balance', state.toBalance(), details);
|
|
|
|
|
this.emit('balance', balance);
|
|
|
|
|
|
|
|
|
|
return details;
|
|
|
|
|
};
|
|
|
|
|
@ -688,12 +680,10 @@ TXDB.prototype.insert = async function insert(wtx, block) {
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.confirm = async function confirm(wtx, block) {
|
|
|
|
|
const b = this.bucket();
|
|
|
|
|
const state = this.state.clone();
|
|
|
|
|
const tx = wtx.tx;
|
|
|
|
|
const hash = wtx.hash;
|
|
|
|
|
const {tx, hash} = wtx;
|
|
|
|
|
const height = block.height;
|
|
|
|
|
const details = new Details(this, wtx, block);
|
|
|
|
|
const accounts = new Set();
|
|
|
|
|
const state = new BalanceDelta();
|
|
|
|
|
|
|
|
|
|
wtx.setBlock(block);
|
|
|
|
|
|
|
|
|
|
@ -719,6 +709,9 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
|
|
|
|
|
if (!credit)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const path = await this.getPath(credit.coin);
|
|
|
|
|
assert(path);
|
|
|
|
|
|
|
|
|
|
// Add a spend record and undo coin
|
|
|
|
|
// for the coin we now know is ours.
|
|
|
|
|
// We don't need to remove the coin
|
|
|
|
|
@ -726,8 +719,8 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
|
|
|
|
|
// first place.
|
|
|
|
|
this.spendCredit(b, credit, tx, i);
|
|
|
|
|
|
|
|
|
|
state.coin -= 1;
|
|
|
|
|
state.unconfirmed -= credit.coin.value;
|
|
|
|
|
state.coin(path, -1);
|
|
|
|
|
state.unconfirmed(path, -credit.coin.value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const coin = credit.coin;
|
|
|
|
|
@ -738,12 +731,11 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
|
|
|
|
|
assert(path);
|
|
|
|
|
|
|
|
|
|
details.setInput(i, path, coin);
|
|
|
|
|
accounts.add(path.account);
|
|
|
|
|
|
|
|
|
|
// We can now safely remove the credit
|
|
|
|
|
// entirely, now that we know it's also
|
|
|
|
|
// been removed on-chain.
|
|
|
|
|
state.confirmed -= coin.value;
|
|
|
|
|
state.confirmed(path, -coin.value);
|
|
|
|
|
|
|
|
|
|
await this.removeCredit(b, credit, path);
|
|
|
|
|
}
|
|
|
|
|
@ -758,7 +750,6 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
details.setOutput(i, path);
|
|
|
|
|
accounts.add(path.account);
|
|
|
|
|
|
|
|
|
|
const credit = await this.getCredit(hash, i);
|
|
|
|
|
assert(credit);
|
|
|
|
|
@ -772,7 +763,7 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
|
|
|
|
|
|
|
|
|
|
// Update coin height and confirmed
|
|
|
|
|
// balance. Save once again.
|
|
|
|
|
state.confirmed += output.value;
|
|
|
|
|
state.confirmed(path, output.value);
|
|
|
|
|
credit.coin.height = height;
|
|
|
|
|
|
|
|
|
|
await this.saveCredit(b, credit, path);
|
|
|
|
|
@ -786,25 +777,25 @@ TXDB.prototype.confirm = async function confirm(wtx, block) {
|
|
|
|
|
b.put(layout.h(height, hash), null);
|
|
|
|
|
|
|
|
|
|
// Secondary indexing also needs to change.
|
|
|
|
|
for (const account of accounts) {
|
|
|
|
|
b.del(layout.P(account, hash));
|
|
|
|
|
b.put(layout.H(account, height, hash), null);
|
|
|
|
|
for (const [acct, delta] of state.accounts) {
|
|
|
|
|
await this.updateAccountBalance(b, acct, delta);
|
|
|
|
|
b.del(layout.P(acct, hash));
|
|
|
|
|
b.put(layout.H(acct, height, hash), null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.removeTXMap(b, hash);
|
|
|
|
|
await this.addBlockMap(b, height);
|
|
|
|
|
await this.addBlock(b, tx.hash(), block);
|
|
|
|
|
|
|
|
|
|
// Commit the new state. The balance has updated.
|
|
|
|
|
b.put(layout.R, state.commit());
|
|
|
|
|
const balance = await this.updateBalance(b, state);
|
|
|
|
|
|
|
|
|
|
await b.write();
|
|
|
|
|
|
|
|
|
|
this.state = state;
|
|
|
|
|
|
|
|
|
|
this.unlockTX(tx);
|
|
|
|
|
|
|
|
|
|
this.emit('confirmed', tx, details);
|
|
|
|
|
this.emit('balance', state.toBalance(), details);
|
|
|
|
|
this.emit('balance', balance);
|
|
|
|
|
|
|
|
|
|
return details;
|
|
|
|
|
};
|
|
|
|
|
@ -822,7 +813,7 @@ TXDB.prototype.remove = async function remove(hash) {
|
|
|
|
|
if (!wtx)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return await this.removeRecursive(wtx);
|
|
|
|
|
return this.removeRecursive(wtx);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -835,11 +826,10 @@ TXDB.prototype.remove = async function remove(hash) {
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.erase = async function erase(wtx, block) {
|
|
|
|
|
const b = this.bucket();
|
|
|
|
|
const state = this.state.clone();
|
|
|
|
|
const {tx, hash} = wtx;
|
|
|
|
|
const height = block ? block.height : -1;
|
|
|
|
|
const details = new Details(this, wtx, block);
|
|
|
|
|
const accounts = new Set();
|
|
|
|
|
const state = new BalanceDelta();
|
|
|
|
|
|
|
|
|
|
if (!tx.isCoinbase()) {
|
|
|
|
|
// We need to undo every part of the
|
|
|
|
|
@ -861,18 +851,20 @@ TXDB.prototype.erase = async function erase(wtx, block) {
|
|
|
|
|
assert(path);
|
|
|
|
|
|
|
|
|
|
details.setInput(i, path, coin);
|
|
|
|
|
accounts.add(path.account);
|
|
|
|
|
|
|
|
|
|
// Recalculate the balance, remove
|
|
|
|
|
// from stxo set, remove the undo
|
|
|
|
|
// coin, and resave the credit.
|
|
|
|
|
state.coin += 1;
|
|
|
|
|
state.unconfirmed += coin.value;
|
|
|
|
|
state.tx(path, -1);
|
|
|
|
|
state.coin(path, 1);
|
|
|
|
|
state.unconfirmed(path, coin.value);
|
|
|
|
|
|
|
|
|
|
if (block)
|
|
|
|
|
state.confirmed += coin.value;
|
|
|
|
|
state.confirmed(path, coin.value);
|
|
|
|
|
|
|
|
|
|
this.unspendCredit(b, tx, i);
|
|
|
|
|
|
|
|
|
|
credit.spent = false;
|
|
|
|
|
await this.saveCredit(b, credit, path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -887,15 +879,15 @@ TXDB.prototype.erase = async function erase(wtx, block) {
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
details.setOutput(i, path);
|
|
|
|
|
accounts.add(path.account);
|
|
|
|
|
|
|
|
|
|
const credit = Credit.fromTX(tx, i, height);
|
|
|
|
|
|
|
|
|
|
state.coin -= 1;
|
|
|
|
|
state.unconfirmed -= output.value;
|
|
|
|
|
state.tx(path, -1);
|
|
|
|
|
state.coin(path, -1);
|
|
|
|
|
state.unconfirmed(path, -output.value);
|
|
|
|
|
|
|
|
|
|
if (block)
|
|
|
|
|
state.confirmed -= output.value;
|
|
|
|
|
state.confirmed(path, -output.value);
|
|
|
|
|
|
|
|
|
|
await this.removeCredit(b, credit, path);
|
|
|
|
|
}
|
|
|
|
|
@ -911,36 +903,35 @@ TXDB.prototype.erase = async function erase(wtx, block) {
|
|
|
|
|
b.del(layout.h(height, hash));
|
|
|
|
|
|
|
|
|
|
// Remove all secondary indexing.
|
|
|
|
|
for (const account of accounts) {
|
|
|
|
|
b.del(layout.T(account, hash));
|
|
|
|
|
b.del(layout.M(account, wtx.mtime, hash));
|
|
|
|
|
for (const [acct, delta] of state.accounts) {
|
|
|
|
|
await this.updateAccountBalance(b, acct, delta);
|
|
|
|
|
|
|
|
|
|
b.del(layout.T(acct, hash));
|
|
|
|
|
b.del(layout.M(acct, wtx.mtime, hash));
|
|
|
|
|
|
|
|
|
|
if (!block)
|
|
|
|
|
b.del(layout.P(account, hash));
|
|
|
|
|
b.del(layout.P(acct, hash));
|
|
|
|
|
else
|
|
|
|
|
b.del(layout.H(account, height, hash));
|
|
|
|
|
b.del(layout.H(acct, height, hash));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.removeTXMap(b, hash);
|
|
|
|
|
|
|
|
|
|
// Update block records.
|
|
|
|
|
if (block) {
|
|
|
|
|
await this.removeBlockMap(b, height);
|
|
|
|
|
await this.spliceBlock(b, hash, height);
|
|
|
|
|
} else {
|
|
|
|
|
await this.removeTXMap(b, hash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the transaction counter
|
|
|
|
|
// and commit new state due to
|
|
|
|
|
// balance change.
|
|
|
|
|
state.tx -= 1;
|
|
|
|
|
b.put(layout.R, state.commit());
|
|
|
|
|
const balance = await this.updateBalance(b, state);
|
|
|
|
|
|
|
|
|
|
await b.write();
|
|
|
|
|
|
|
|
|
|
this.state = state;
|
|
|
|
|
|
|
|
|
|
this.emit('remove tx', tx, details);
|
|
|
|
|
this.emit('balance', state.toBalance(), details);
|
|
|
|
|
this.emit('balance', balance);
|
|
|
|
|
|
|
|
|
|
return details;
|
|
|
|
|
};
|
|
|
|
|
@ -954,8 +945,7 @@ TXDB.prototype.erase = async function erase(wtx, block) {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.removeRecursive = async function removeRecursive(wtx) {
|
|
|
|
|
const tx = wtx.tx;
|
|
|
|
|
const hash = wtx.hash;
|
|
|
|
|
const {tx, hash} = wtx;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < tx.outputs.length; i++) {
|
|
|
|
|
const spent = await this.getSpent(hash, i);
|
|
|
|
|
@ -972,11 +962,7 @@ TXDB.prototype.removeRecursive = async function removeRecursive(wtx) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove the spender.
|
|
|
|
|
const details = await this.erase(wtx, wtx.getBlock());
|
|
|
|
|
|
|
|
|
|
assert(details);
|
|
|
|
|
|
|
|
|
|
return details;
|
|
|
|
|
return this.erase(wtx, wtx.getBlock());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -1017,7 +1003,7 @@ TXDB.prototype.unconfirm = async function unconfirm(hash) {
|
|
|
|
|
if (wtx.height === -1)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return await this.disconnect(wtx, wtx.getBlock());
|
|
|
|
|
return this.disconnect(wtx, wtx.getBlock());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -1028,10 +1014,9 @@ TXDB.prototype.unconfirm = async function unconfirm(hash) {
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.disconnect = async function disconnect(wtx, block) {
|
|
|
|
|
const b = this.bucket();
|
|
|
|
|
const state = this.state.clone();
|
|
|
|
|
const {tx, hash, height} = wtx;
|
|
|
|
|
const details = new Details(this, wtx, block);
|
|
|
|
|
const accounts = new Set();
|
|
|
|
|
const state = new BalanceDelta();
|
|
|
|
|
|
|
|
|
|
assert(block);
|
|
|
|
|
|
|
|
|
|
@ -1059,9 +1044,8 @@ TXDB.prototype.disconnect = async function disconnect(wtx, block) {
|
|
|
|
|
assert(path);
|
|
|
|
|
|
|
|
|
|
details.setInput(i, path, coin);
|
|
|
|
|
accounts.add(path.account);
|
|
|
|
|
|
|
|
|
|
state.confirmed += coin.value;
|
|
|
|
|
state.confirmed(path, coin.value);
|
|
|
|
|
|
|
|
|
|
// Resave the credit and mark it
|
|
|
|
|
// as spent in the mempool instead.
|
|
|
|
|
@ -1091,18 +1075,17 @@ TXDB.prototype.disconnect = async function disconnect(wtx, block) {
|
|
|
|
|
await this.updateSpentCoin(b, tx, i, height);
|
|
|
|
|
|
|
|
|
|
details.setOutput(i, path);
|
|
|
|
|
accounts.add(path.account);
|
|
|
|
|
|
|
|
|
|
// Update coin height and confirmed
|
|
|
|
|
// balance. Save once again.
|
|
|
|
|
const coin = credit.coin;
|
|
|
|
|
coin.height = -1;
|
|
|
|
|
credit.coin.height = -1;
|
|
|
|
|
|
|
|
|
|
state.confirmed -= output.value;
|
|
|
|
|
state.confirmed(path, -output.value);
|
|
|
|
|
|
|
|
|
|
await this.saveCredit(b, credit, path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.addTXMap(b, hash);
|
|
|
|
|
await this.removeBlockMap(b, height);
|
|
|
|
|
await this.removeBlock(b, tx.hash(), height);
|
|
|
|
|
|
|
|
|
|
@ -1114,21 +1097,20 @@ TXDB.prototype.disconnect = async function disconnect(wtx, block) {
|
|
|
|
|
b.del(layout.h(height, hash));
|
|
|
|
|
|
|
|
|
|
// Secondary indexing also needs to change.
|
|
|
|
|
for (const account of accounts) {
|
|
|
|
|
b.put(layout.P(account, hash), null);
|
|
|
|
|
b.del(layout.H(account, height, hash));
|
|
|
|
|
for (const [acct, delta] of state.accounts) {
|
|
|
|
|
await this.updateAccountBalance(b, acct, delta);
|
|
|
|
|
b.put(layout.P(acct, hash), null);
|
|
|
|
|
b.del(layout.H(acct, height, hash));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Commit state due to unconfirmed
|
|
|
|
|
// vs. confirmed balance change.
|
|
|
|
|
b.put(layout.R, state.commit());
|
|
|
|
|
const balance = await this.updateBalance(b, state);
|
|
|
|
|
|
|
|
|
|
await b.write();
|
|
|
|
|
|
|
|
|
|
this.state = state;
|
|
|
|
|
|
|
|
|
|
this.emit('unconfirmed', tx, details);
|
|
|
|
|
this.emit('balance', state.toBalance(), details);
|
|
|
|
|
this.emit('balance', balance);
|
|
|
|
|
|
|
|
|
|
return details;
|
|
|
|
|
};
|
|
|
|
|
@ -1304,6 +1286,7 @@ TXDB.prototype.getLocked = function getLocked() {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.getAccountHistoryHashes = function getAccountHistoryHashes(acct) {
|
|
|
|
|
assert(typeof acct === 'number');
|
|
|
|
|
return this.keys({
|
|
|
|
|
gte: layout.T(acct, encoding.NULL_HASH),
|
|
|
|
|
lte: layout.T(acct, encoding.HIGH_HASH),
|
|
|
|
|
@ -1340,6 +1323,7 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(acct) {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.getAccountPendingHashes = function getAccountPendingHashes(acct) {
|
|
|
|
|
assert(typeof acct === 'number');
|
|
|
|
|
return this.keys({
|
|
|
|
|
gte: layout.P(acct, encoding.NULL_HASH),
|
|
|
|
|
lte: layout.P(acct, encoding.HIGH_HASH),
|
|
|
|
|
@ -1376,6 +1360,7 @@ TXDB.prototype.getPendingHashes = function getPendingHashes(acct) {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.getAccountOutpoints = function getAccountOutpoints(acct) {
|
|
|
|
|
assert(typeof acct === 'number');
|
|
|
|
|
return this.keys({
|
|
|
|
|
gte: layout.C(acct, encoding.NULL_HASH, 0),
|
|
|
|
|
lte: layout.C(acct, encoding.HIGH_HASH, 0xffffffff),
|
|
|
|
|
@ -1420,6 +1405,8 @@ TXDB.prototype.getOutpoints = function getOutpoints(acct) {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.getAccountHeightRangeHashes = function getAccountHeightRangeHashes(acct, options) {
|
|
|
|
|
assert(typeof acct === 'number');
|
|
|
|
|
|
|
|
|
|
const start = options.start || 0;
|
|
|
|
|
const end = options.end || 0xffffffff;
|
|
|
|
|
|
|
|
|
|
@ -1489,6 +1476,8 @@ TXDB.prototype.getHeightHashes = function getHeightHashes(height) {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.getAccountRangeHashes = function getAccountRangeHashes(acct, options) {
|
|
|
|
|
assert(typeof acct === 'number');
|
|
|
|
|
|
|
|
|
|
const start = options.start || 0;
|
|
|
|
|
const end = options.end || 0xffffffff;
|
|
|
|
|
|
|
|
|
|
@ -1828,20 +1817,6 @@ TXDB.prototype.getSpentView = async function getSpentView(tx) {
|
|
|
|
|
return view;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get TXDB state.
|
|
|
|
|
* @returns {Promise}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.getState = async function getState() {
|
|
|
|
|
const data = await this.get(layout.R);
|
|
|
|
|
|
|
|
|
|
if (!data)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return TXDBState.fromRaw(this.wid, this.id, data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get transaction.
|
|
|
|
|
* @param {Hash} hash
|
|
|
|
|
@ -1869,7 +1844,7 @@ TXDB.prototype.getDetails = async function getDetails(hash) {
|
|
|
|
|
if (!wtx)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return await this.toDetails(wtx);
|
|
|
|
|
return this.toDetails(wtx);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -1882,7 +1857,7 @@ TXDB.prototype.toDetails = async function toDetails(wtxs) {
|
|
|
|
|
const out = [];
|
|
|
|
|
|
|
|
|
|
if (!Array.isArray(wtxs))
|
|
|
|
|
return await this._toDetails(wtxs);
|
|
|
|
|
return this._toDetails(wtxs);
|
|
|
|
|
|
|
|
|
|
for (const wtx of wtxs) {
|
|
|
|
|
const details = await this._toDetails(wtx);
|
|
|
|
|
@ -2036,7 +2011,7 @@ TXDB.prototype.updateSpentCoin = async function updateSpentCoin(b, tx, index, he
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.hasCoin = async function hasCoin(hash, index) {
|
|
|
|
|
return await this.has(layout.c(hash, index));
|
|
|
|
|
return this.has(layout.c(hash, index));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -2048,12 +2023,10 @@ TXDB.prototype.hasCoin = async function hasCoin(hash, index) {
|
|
|
|
|
TXDB.prototype.getBalance = async function getBalance(acct) {
|
|
|
|
|
assert(typeof acct === 'number');
|
|
|
|
|
|
|
|
|
|
// Slow case
|
|
|
|
|
if (acct !== -1)
|
|
|
|
|
return await this.getAccountBalance(acct);
|
|
|
|
|
return this.getAccountBalance(acct);
|
|
|
|
|
|
|
|
|
|
// Fast case
|
|
|
|
|
return this.state.toBalance();
|
|
|
|
|
return this.getWalletBalance();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -2062,20 +2035,12 @@ TXDB.prototype.getBalance = async function getBalance(acct) {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.getWalletBalance = async function getWalletBalance() {
|
|
|
|
|
const credits = await this.getCredits();
|
|
|
|
|
const balance = new Balance(this.wid, this.id, -1);
|
|
|
|
|
const data = await this.get(layout.R);
|
|
|
|
|
|
|
|
|
|
for (const credit of credits) {
|
|
|
|
|
const coin = credit.coin;
|
|
|
|
|
if (!data)
|
|
|
|
|
return new Balance();
|
|
|
|
|
|
|
|
|
|
if (coin.height !== -1)
|
|
|
|
|
balance.confirmed += coin.value;
|
|
|
|
|
|
|
|
|
|
if (!credit.spent)
|
|
|
|
|
balance.unconfirmed += coin.value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return balance;
|
|
|
|
|
return Balance.fromRaw(-1, data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -2085,20 +2050,12 @@ TXDB.prototype.getWalletBalance = async function getWalletBalance() {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDB.prototype.getAccountBalance = async function getAccountBalance(acct) {
|
|
|
|
|
const credits = await this.getAccountCredits(acct);
|
|
|
|
|
const balance = new Balance(this.wid, this.id, acct);
|
|
|
|
|
const data = await this.get(layout.r(acct));
|
|
|
|
|
|
|
|
|
|
for (const credit of credits) {
|
|
|
|
|
const coin = credit.coin;
|
|
|
|
|
if (!data)
|
|
|
|
|
return new Balance(acct);
|
|
|
|
|
|
|
|
|
|
if (coin.height !== -1)
|
|
|
|
|
balance.confirmed += coin.value;
|
|
|
|
|
|
|
|
|
|
if (!credit.spent)
|
|
|
|
|
balance.unconfirmed += coin.value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return balance;
|
|
|
|
|
return Balance.fromRaw(acct, data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -2149,138 +2106,47 @@ TXDB.prototype.abandon = async function abandon(hash) {
|
|
|
|
|
if (!result)
|
|
|
|
|
throw new Error('TX not eligible.');
|
|
|
|
|
|
|
|
|
|
return await this.remove(hash);
|
|
|
|
|
return this.remove(hash);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Balance
|
|
|
|
|
* @alias module:wallet.Balance
|
|
|
|
|
* @constructor
|
|
|
|
|
* @param {WalletID} wid
|
|
|
|
|
* @param {String} id
|
|
|
|
|
* @param {Number} account
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
function Balance(wid, id, account) {
|
|
|
|
|
function Balance(acct = -1) {
|
|
|
|
|
if (!(this instanceof Balance))
|
|
|
|
|
return new Balance(wid, id, account);
|
|
|
|
|
return new Balance(acct);
|
|
|
|
|
|
|
|
|
|
this.wid = wid;
|
|
|
|
|
this.id = id;
|
|
|
|
|
this.account = account;
|
|
|
|
|
this.unconfirmed = 0;
|
|
|
|
|
this.confirmed = 0;
|
|
|
|
|
}
|
|
|
|
|
assert(typeof acct === 'number');
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test whether a balance is equal.
|
|
|
|
|
* @param {Balance} balance
|
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Balance.prototype.equal = function equal(balance) {
|
|
|
|
|
return this.wid === balance.wid
|
|
|
|
|
&& this.confirmed === balance.confirmed
|
|
|
|
|
&& this.unconfirmed === balance.unconfirmed;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert balance to a more json-friendly object.
|
|
|
|
|
* @param {Boolean?} minimal
|
|
|
|
|
* @returns {Object}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Balance.prototype.toJSON = function toJSON(minimal) {
|
|
|
|
|
return {
|
|
|
|
|
wid: !minimal ? this.wid : undefined,
|
|
|
|
|
id: !minimal ? this.id : undefined,
|
|
|
|
|
account: !minimal ? this.account : undefined,
|
|
|
|
|
unconfirmed: this.unconfirmed,
|
|
|
|
|
confirmed: this.confirmed
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert balance to human-readable string.
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Balance.prototype.toString = function toString() {
|
|
|
|
|
return '<Balance'
|
|
|
|
|
+ ` unconfirmed=${Amount.btc(this.unconfirmed)}`
|
|
|
|
|
+ ` confirmed=${Amount.btc(this.confirmed)}`
|
|
|
|
|
+ '>';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Inspect balance.
|
|
|
|
|
* @param {String}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Balance.prototype.inspect = function inspect() {
|
|
|
|
|
return this.toString();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Chain State
|
|
|
|
|
* @alias module:wallet.ChainState
|
|
|
|
|
* @constructor
|
|
|
|
|
* @param {WalletID} wid
|
|
|
|
|
* @param {String} id
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
function TXDBState(wid, id) {
|
|
|
|
|
this.wid = wid;
|
|
|
|
|
this.id = id;
|
|
|
|
|
this.account = acct;
|
|
|
|
|
this.tx = 0;
|
|
|
|
|
this.coin = 0;
|
|
|
|
|
this.unconfirmed = 0;
|
|
|
|
|
this.confirmed = 0;
|
|
|
|
|
this.committed = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clone the state.
|
|
|
|
|
* @returns {TXDBState}
|
|
|
|
|
* Apply delta.
|
|
|
|
|
* @param {Balance} balance
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDBState.prototype.clone = function clone() {
|
|
|
|
|
const state = new TXDBState(this.wid, this.id);
|
|
|
|
|
state.tx = this.tx;
|
|
|
|
|
state.coin = this.coin;
|
|
|
|
|
state.unconfirmed = this.unconfirmed;
|
|
|
|
|
state.confirmed = this.confirmed;
|
|
|
|
|
return state;
|
|
|
|
|
Balance.prototype.applyTo = function applyTo(balance) {
|
|
|
|
|
balance.tx += this.tx;
|
|
|
|
|
balance.coin += this.coin;
|
|
|
|
|
balance.unconfirmed += this.unconfirmed;
|
|
|
|
|
balance.confirmed += this.confirmed;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Commit and serialize state.
|
|
|
|
|
* Serialize balance.
|
|
|
|
|
* @returns {Buffer}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDBState.prototype.commit = function commit() {
|
|
|
|
|
this.committed = true;
|
|
|
|
|
return this.toRaw();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert state to a balance object.
|
|
|
|
|
* @returns {Balance}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDBState.prototype.toBalance = function toBalance() {
|
|
|
|
|
const balance = new Balance(this.wid, this.id, -1);
|
|
|
|
|
balance.unconfirmed = this.unconfirmed;
|
|
|
|
|
balance.confirmed = this.confirmed;
|
|
|
|
|
return balance;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Serialize state.
|
|
|
|
|
* @returns {Buffer}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDBState.prototype.toRaw = function toRaw() {
|
|
|
|
|
Balance.prototype.toRaw = function toRaw() {
|
|
|
|
|
const bw = new StaticWriter(32);
|
|
|
|
|
|
|
|
|
|
bw.writeU64(this.tx);
|
|
|
|
|
@ -2298,7 +2164,7 @@ TXDBState.prototype.toRaw = function toRaw() {
|
|
|
|
|
* @returns {TXDBState}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDBState.prototype.fromRaw = function fromRaw(data) {
|
|
|
|
|
Balance.prototype.fromRaw = function fromRaw(data) {
|
|
|
|
|
const br = new BufferReader(data);
|
|
|
|
|
this.tx = br.readU64();
|
|
|
|
|
this.coin = br.readU64();
|
|
|
|
|
@ -2308,25 +2174,25 @@ TXDBState.prototype.fromRaw = function fromRaw(data) {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Instantiate txdb state from serialized data.
|
|
|
|
|
* Instantiate balance from serialized data.
|
|
|
|
|
* @param {Number} acct
|
|
|
|
|
* @param {Buffer} data
|
|
|
|
|
* @returns {TXDBState}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDBState.fromRaw = function fromRaw(wid, id, data) {
|
|
|
|
|
return new TXDBState(wid, id).fromRaw(data);
|
|
|
|
|
Balance.fromRaw = function fromRaw(acct, data) {
|
|
|
|
|
return new Balance(acct).fromRaw(data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert state to a more json-friendly object.
|
|
|
|
|
* Convert balance to a more json-friendly object.
|
|
|
|
|
* @param {Boolean?} minimal
|
|
|
|
|
* @returns {Object}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDBState.prototype.toJSON = function toJSON(minimal) {
|
|
|
|
|
Balance.prototype.toJSON = function toJSON(minimal) {
|
|
|
|
|
return {
|
|
|
|
|
wid: !minimal ? this.wid : undefined,
|
|
|
|
|
id: !minimal ? this.id : undefined,
|
|
|
|
|
account: !minimal ? this.account : undefined,
|
|
|
|
|
tx: this.tx,
|
|
|
|
|
coin: this.coin,
|
|
|
|
|
unconfirmed: this.unconfirmed,
|
|
|
|
|
@ -2335,12 +2201,68 @@ TXDBState.prototype.toJSON = function toJSON(minimal) {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Inspect the state.
|
|
|
|
|
* @returns {Object}
|
|
|
|
|
* Inspect balance.
|
|
|
|
|
* @param {String}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
TXDBState.prototype.inspect = function inspect() {
|
|
|
|
|
return this.toJSON();
|
|
|
|
|
Balance.prototype.inspect = function inspect() {
|
|
|
|
|
return this;
|
|
|
|
|
return '<Balance'
|
|
|
|
|
+ ` tx=${this.tx}`
|
|
|
|
|
+ ` coin=${this.coin}`
|
|
|
|
|
+ ` unconfirmed=${Amount.btc(this.unconfirmed)}`
|
|
|
|
|
+ ` confirmed=${Amount.btc(this.confirmed)}`
|
|
|
|
|
+ '>';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Balance Delta
|
|
|
|
|
* @constructor
|
|
|
|
|
* @ignore
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
function BalanceDelta() {
|
|
|
|
|
this.wallet = new Balance();
|
|
|
|
|
this.accounts = new Map();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BalanceDelta.prototype.updated = function updated() {
|
|
|
|
|
return this.wallet.tx !== 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BalanceDelta.prototype.applyTo = function applyTo(balance) {
|
|
|
|
|
this.wallet.applyTo(balance);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BalanceDelta.prototype.get = function get(path) {
|
|
|
|
|
if (!this.accounts.has(path.account))
|
|
|
|
|
this.accounts.set(path.account, new Balance());
|
|
|
|
|
|
|
|
|
|
return this.accounts.get(path.account);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BalanceDelta.prototype.tx = function tx(path, value) {
|
|
|
|
|
const account = this.get(path);
|
|
|
|
|
account.tx = value;
|
|
|
|
|
this.wallet.tx = value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BalanceDelta.prototype.coin = function coin(path, value) {
|
|
|
|
|
const account = this.get(path);
|
|
|
|
|
account.coin += value;
|
|
|
|
|
this.wallet.coin += value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BalanceDelta.prototype.unconfirmed = function unconfirmed(path, value) {
|
|
|
|
|
const account = this.get(path);
|
|
|
|
|
account.unconfirmed += value;
|
|
|
|
|
this.wallet.unconfirmed += value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BalanceDelta.prototype.confirmed = function confirmed(path, value) {
|
|
|
|
|
const account = this.get(path);
|
|
|
|
|
account.confirmed += value;
|
|
|
|
|
this.wallet.confirmed += value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|