refactor: chain. txdb. wallet.

This commit is contained in:
Christopher Jeffrey 2016-09-22 23:58:19 -07:00
parent 63a9dc61f6
commit c2e1e4bfc9
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
13 changed files with 152 additions and 171 deletions

View File

@ -117,8 +117,4 @@ var runBench = co(function* runBench() {
end(1);
});
runBench().then(process.exit).catch(function(err) {
utils.nextTick(function() {
throw err;
});
});
runBench().then(process.exit);

View File

@ -52,7 +52,6 @@ known-peers: ./known-peers
# Miner
# payout-address: 1111111111111111111114oLvT2
# coinbase-flags: mined by bcoin
# parallel: false
# HTTP
# ssl-cert: @/ssl/cert.crt

View File

@ -1008,7 +1008,9 @@ Chain.prototype.isBusy = function isBusy() {
Chain.prototype.add = co(function* add(block) {
var unlock = yield this.locker.lock(block);
this.currentBlock = block.hash('hex');
try {
return yield this._add(block);
} finally {
@ -1232,7 +1234,7 @@ Chain.prototype._add = co(function* add(block) {
* @returns {Boolean}
*/
Chain.prototype._isSlow = function _isSlow() {
Chain.prototype.isSlow = function isSlow() {
if (this.options.spv)
return false;
@ -1265,7 +1267,7 @@ Chain.prototype.finish = function finish(block, entry) {
// Keep track of total blocks handled.
this.total += 1;
if (!this._isSlow())
if (!this.isSlow())
return;
// Report memory for debugging.
@ -1777,8 +1779,7 @@ Chain.prototype.getState = co(function* getState(prev, id) {
var timeStart, timeTimeout, compute, height;
var i, entry, count, state, block, medianTime;
if (!deployment)
return constants.thresholdStates.FAILED;
assert(deployment);
timeStart = deployment.startTime;
timeTimeout = deployment.timeout;
@ -1791,11 +1792,10 @@ Chain.prototype.getState = co(function* getState(prev, id) {
height = prev.height - ((prev.height + 1) % period);
prev = yield prev.getAncestorByHeight(height);
if (!prev)
return constants.thresholdStates.FAILED;
assert(prev.height === height);
assert(((prev.height + 1) % period) === 0);
if (prev) {
assert(prev.height === height);
assert(((prev.height + 1) % period) === 0);
}
}
entry = prev;
@ -1811,6 +1811,7 @@ Chain.prototype.getState = co(function* getState(prev, id) {
if (medianTime < timeStart) {
state = constants.thresholdStates.DEFINED;
stateCache[entry.hash] = state;
break;
}
@ -1830,58 +1831,50 @@ Chain.prototype.getState = co(function* getState(prev, id) {
if (medianTime >= timeTimeout) {
state = constants.thresholdStates.FAILED;
stateCache[entry.hash] = state;
continue;
break;
}
if (medianTime >= timeStart) {
state = constants.thresholdStates.STARTED;
stateCache[entry.hash] = state;
continue;
break;
}
stateCache[entry.hash] = state;
continue;
break;
case constants.thresholdStates.STARTED:
medianTime = yield entry.getMedianTimeAsync();
if (medianTime >= timeTimeout) {
state = constants.thresholdStates.FAILED;
stateCache[entry.hash] = state;
break;
}
i = 0;
count = 0;
block = entry;
count = 0;
while (block) {
if (i++ >= period)
break;
if (hasBit(block, deployment))
for (i = 0; i < period; i++) {
if (block.hasBit(deployment))
count++;
block = yield block.getPrevious();
assert(block);
}
if (count >= threshold)
state = constants.thresholdStates.LOCKED_IN;
stateCache[entry.hash] = state;
break;
case constants.thresholdStates.LOCKED_IN:
state = constants.thresholdStates.ACTIVE;
stateCache[entry.hash] = state;
break;
case constants.thresholdStates.FAILED:
case constants.thresholdStates.ACTIVE:
stateCache[entry.hash] = state;
break;
default:
assert(false, 'Bad state.');
break;
}
stateCache[entry.hash] = state;
}
return state;
@ -1984,7 +1977,7 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) {
var i, input, entry;
if (tx.isCoinbase() || tx.version < 2 || !hasFlag)
return [minHeight, minTime];
return new LockTimes(minHeight, minTime);
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
@ -1992,9 +1985,9 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) {
if (input.sequence & disableFlag)
continue;
coinHeight = input.coin.height === -1
? this.height + 1
: input.coin.height;
coinHeight = input.coin.height !== -1
? input.coin.height
: this.height + 1;
if ((input.sequence & typeFlag) === 0) {
coinHeight += (input.sequence & mask) - 1;
@ -2010,7 +2003,7 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) {
minTime = Math.max(minTime, coinTime);
}
return [minHeight, minTime];
return new LockTimes(minHeight, minTime);
});
/**
@ -2048,9 +2041,7 @@ Chain.prototype.evalLocks = co(function* evalLocks(prev, minHeight, minTime) {
Chain.prototype.checkLocks = co(function* checkLocks(prev, tx, flags) {
var times = yield this.getLocks(prev, tx, flags);
var minHeight = times[0];
var minTime = times[1];
return yield this.evalLocks(prev, minHeight, minTime);
return yield this.evalLocks(prev, times.height, times.time);
});
/**
@ -2135,14 +2126,12 @@ DeploymentState.prototype.hasWitness = function hasWitness() {
};
/*
* Helpers
* LockTimes
*/
function hasBit(entry, deployment) {
var bits = entry.version & constants.versionbits.TOP_MASK;
var topBits = constants.versionbits.TOP_BITS;
var mask = 1 << deployment.bit;
return bits === topBits && (entry.version & mask) !== 0;
function LockTimes(height, time) {
this.height = height;
this.time = time;
}
/*

View File

@ -417,7 +417,8 @@ ChainEntry.prototype.isSuperMajorityAsync = co(function* isSuperMajorityAsync(ve
});
/**
* Test whether the entry is potentially an ancestor of a checkpoint.
* Test whether the entry is potentially
* an ancestor of a checkpoint.
* @returns {Boolean}
*/
@ -429,6 +430,19 @@ ChainEntry.prototype.isHistorical = function isHistorical() {
return false;
};
/**
* Test whether the entry contains a version bit.
* @param {Object} deployment
* @returns {Boolean}
*/
ChainEntry.prototype.hasBit = function hasBit(deployment) {
var bits = this.version & constants.versionbits.TOP_MASK;
var topBits = constants.versionbits.TOP_BITS;
var mask = 1 << deployment.bit;
return bits === topBits && (this.version & mask) !== 0;
};
ChainEntry.prototype.__defineGetter__('rhash', function() {
return utils.revHex(this.hash);
});

View File

@ -304,21 +304,23 @@ LowlevelUp.prototype.has = co(function* has(key) {
LowlevelUp.prototype.iterate = co(function* iterate(options) {
var items = [];
var iter, kv, result;
var parse = options.parse;
var iter, result, data;
assert(typeof options.parse === 'function', 'Parse must be a function.');
assert(typeof parse === 'function', 'Parse must be a function.');
iter = this.iterator(options);
for (;;) {
kv = yield iter.next();
if (!kv)
result = yield iter.next();
if (!result)
return items;
result = options.parse(kv[0], kv[1]);
data = parse(result.key, result.value);
if (result)
items.push(result);
if (data)
items.push(data);
}
return items;
@ -453,7 +455,7 @@ Iterator.prototype.next = function() {
return;
}
resolve([key, value]);
resolve(new KeyValue(key, value));
});
});
};
@ -473,6 +475,11 @@ Iterator.prototype.end = function end() {
* Helpers
*/
function KeyValue(key, value) {
this.key = key;
this.value = value;
}
function isNotFound(err) {
if (!err)
return false;

View File

@ -115,7 +115,7 @@ Miner.prototype._init = function _init() {
if (bcoin.useWorkers) {
this.workerPool = new bcoin.workers({
size: this.options.parallel ? 2 : 1,
size: 1,
timeout: -1
});
@ -268,7 +268,6 @@ Miner.prototype.createBlock = co(function* createBlock(tip) {
address: this.address,
coinbaseFlags: this.coinbaseFlags,
witness: this.chain.segwitActive,
parallel: this.options.parallel,
network: this.network
});

View File

@ -339,6 +339,13 @@ Peer.prototype._bip151 = co(function* _bip151() {
} catch (err) {
this.error(err, true);
}
assert(this.bip151.completed);
if (this.bip151.handshake) {
this.logger.info('BIP151 handshake complete (%s).', this.hostname);
this.logger.info('Connection is encrypted (%s).', this.hostname);
}
});
/**
@ -347,17 +354,7 @@ Peer.prototype._bip151 = co(function* _bip151() {
*/
Peer.prototype._bip150 = co(function* _bip150() {
if (!this.bip151)
return;
assert(this.bip151.completed);
if (this.bip151.handshake) {
this.logger.info('BIP151 handshake complete (%s).', this.hostname);
this.logger.info('Connection is encrypted (%s).', this.hostname);
}
if (!this.bip150)
if (!this.bip151 || !this.bip150)
return;
assert(!this.bip150.completed);
@ -2090,7 +2087,6 @@ Peer.prototype._handleCmpctBlock = co(function* _handleCmpctBlock(packet) {
return;
}
// Sort of a lock too.
this.compactBlocks[hash] = block;
result = block.fillMempool(this.mempool);

View File

@ -124,8 +124,7 @@ function Fullnode(options) {
mempool: this.mempool,
fees: this.fees,
address: this.options.payoutAddress,
coinbaseFlags: this.options.coinbaseFlags,
parallel: this.options.parallel
coinbaseFlags: this.options.coinbaseFlags
});
// Wallet database needs access to fees.

View File

@ -844,6 +844,12 @@ MTX.prototype.template = function template(ring) {
var total = 0;
var i;
if (Array.isArray(ring)) {
for (i = 0; i < ring.length; i++)
total += this.template(ring[i]);
return total;
}
for (i = 0; i < this.inputs.length; i++) {
if (!ring.ownInput(this, i))
continue;

View File

@ -592,13 +592,13 @@ Account.prototype.setDepth = co(function* setDepth(receiveDepth, changeDepth) {
}
if (rings.length === 0)
return [];
return;
yield this.saveAddress(rings);
this.save();
return [receive, change];
return receive;
});
/**

View File

@ -380,19 +380,19 @@ TXDB.prototype.getInfo = function getInfo(tx) {
* to orphan list. Stored by its required coin ID.
* @private
* @param {Outpoint} prevout - Required coin hash & index.
* @param {Buffer} spender - Spender input hash and index.
* @param {Buffer} input - Spender input hash and index.
* @param {Function} callback - Returns [Error, Buffer].
*/
TXDB.prototype.addOrphan = co(function* addOrphan(prevout, spender) {
var p = new BufferWriter();
TXDB.prototype.addOrphan = co(function* addOrphan(prevout, input) {
var key = layout.o(prevout.hash, prevout.index);
var data = yield this.get(key);
var p = new BufferWriter();
if (data)
p.writeBytes(data);
p.writeBytes(spender);
p.writeBytes(input);
this.put(key, p.render());
});
@ -406,24 +406,24 @@ TXDB.prototype.addOrphan = co(function* addOrphan(prevout, spender) {
*/
TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) {
var key = layout.o(hash, index);
var data = yield this.get(key);
var items = [];
var i, data, orphans, orphan, tx, p;
data = yield this.get(layout.o(hash, index));
var i, inputs, input, tx, p;
if (!data)
return;
p = new BufferReader(data);
orphans = [];
inputs = [];
while (p.left())
orphans.push(bcoin.outpoint.fromRaw(p));
inputs.push(bcoin.outpoint.fromRaw(p));
for (i = 0; i < orphans.length; i++) {
orphan = orphans[i];
tx = yield this.getTX(orphan.hash);
items.push([orphan, tx]);
for (i = 0; i < inputs.length; i++) {
input = inputs[i];
tx = yield this.getTX(input.hash);
items.push(new Orphan(input, tx));
}
return items;
@ -439,7 +439,7 @@ TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) {
*/
TXDB.prototype.verify = co(function* verify(tx, info) {
var i, input, prevout, address, coin, spent, rtx, rinfo, result;
var i, input, prevout, address, coin, spent, conflict;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
@ -462,7 +462,7 @@ TXDB.prototype.verify = co(function* verify(tx, info) {
// Skip invalid transactions
if (this.options.verify) {
if (!tx.verifyInput(i))
if (!(yield tx.verifyInputAsync(i)))
return false;
}
@ -487,25 +487,23 @@ TXDB.prototype.verify = co(function* verify(tx, info) {
// Skip invalid transactions
if (this.options.verify) {
if (!tx.verifyInput(i))
if (!(yield tx.verifyInputAsync(i)))
return false;
}
this.logger.warning('Removing conflicting tx: %s.',
utils.revHex(spent.hash));
result = yield this.removeConflict(spent.hash, tx);
// Remove the older double spender.
conflict = yield this.removeConflict(spent.hash, tx);
// Spender was not removed, the current
// transaction is not elligible to be added.
if (!result)
if (!conflict)
return false;
rtx = result[0];
rinfo = result[1];
// Emit the _removed_ transaction.
this.emit('conflict', rtx, rinfo);
this.emit('conflict', conflict.tx, conflict.info);
}
return true;
@ -521,7 +519,7 @@ TXDB.prototype.verify = co(function* verify(tx, info) {
TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) {
var hash = tx.hash('hex');
var i, orphans, coin, item, input, orphan;
var i, orphans, coin, input, orphan, key;
orphans = yield this.getOrphans(hash, index);
@ -534,27 +532,28 @@ TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) {
// Add input to orphan
for (i = 0; i < orphans.length; i++) {
item = orphans[i];
input = item[0];
orphan = item[1];
orphan = orphans[i];
input = orphan.input;
tx = orphan.tx;
// Probably removed by some other means.
if (!orphan)
if (!tx)
continue;
orphan.inputs[input.index].coin = coin;
tx.inputs[input.index].coin = coin;
assert(orphan.inputs[input.index].prevout.hash === hash);
assert(orphan.inputs[input.index].prevout.index === index);
assert(tx.inputs[input.index].prevout.hash === hash);
assert(tx.inputs[input.index].prevout.index === index);
// Verify that input script is correct, if not - add
// output to unspent and remove orphan from storage
if (!this.options.verify || (yield orphan.verifyInputAsync(input.index))) {
this.put(layout.d(input.hash, input.index), coin.toRaw());
if (!this.options.verify || (yield tx.verifyInputAsync(input.index))) {
key = layout.d(input.hash, input.index);
this.put(key, coin.toRaw());
return true;
}
yield this.lazyRemove(orphan);
yield this.lazyRemove(tx);
}
// Just going to be added again outside.
@ -621,11 +620,14 @@ TXDB.prototype._add = co(function* add(tx, info) {
for (i = 0; i < info.accounts.length; i++) {
account = info.accounts[i];
this.put(layout.T(account, hash), DUMMY);
if (tx.ts === 0)
this.put(layout.P(account, hash), DUMMY);
else
this.put(layout.H(account, tx.height, hash), DUMMY);
this.put(layout.M(account, tx.ps, hash), DUMMY);
}
@ -646,7 +648,7 @@ TXDB.prototype._add = co(function* add(tx, info) {
key = prevout.hash + prevout.index;
// s/[outpoint-key] -> [spender-hash]|[spender-input-index]
// s[outpoint-key] -> [spender-hash]|[spender-input-index]
spender = bcoin.outpoint.fromTX(tx, i).toRaw();
this.put(layout.s(prevout.hash, prevout.index), spender);
@ -757,7 +759,7 @@ TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref) {
info = yield this.removeRecursive(tx);
return [tx, info];
return new Conflict(tx, info);
});
/**
@ -774,6 +776,7 @@ TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) {
for (i = 0; i < tx.outputs.length; i++) {
spent = yield this.isSpent(hash, i);
if (!spent)
continue;
@ -1019,11 +1022,14 @@ TXDB.prototype.__remove = co(function* remove(tx, info) {
for (i = 0; i < info.accounts.length; i++) {
account = info.accounts[i];
this.del(layout.T(account, hash));
if (tx.ts === 0)
this.del(layout.P(account, hash));
else
this.del(layout.H(account, tx.height, hash));
this.del(layout.M(account, tx.ps, hash));
}
@ -2092,6 +2098,16 @@ function sortCoins(coins) {
});
}
function Conflict(tx, info) {
this.tx = tx;
this.info = info;
}
function Orphan(input, tx) {
this.input = input;
this.tx = tx;
}
/*
* Expose
*/

View File

@ -56,7 +56,6 @@ function Wallet(db, options) {
this.db = db;
this.network = db.network;
this.logger = db.logger;
this.workerPool = db.workerPool;
this.writeLock = new bcoin.locker(this);
this.fundLock = new bcoin.locker(this);
@ -1220,8 +1219,7 @@ Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) {
var receive = [];
var accounts = {};
var i, j, path, paths, account;
var receiveDepth, changeDepth;
var ret, rcv, chng;
var receiveDepth, changeDepth, ring;
this.start();
@ -1265,22 +1263,19 @@ Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) {
if (!account)
continue;
ret = yield account.setDepth(receiveDepth, changeDepth);
ring = yield account.setDepth(receiveDepth, changeDepth);
rcv = ret[0];
chng = ret[1];
if (rcv)
receive.push(rcv);
if (ring)
receive.push(ring);
}
yield this.commit();
if (receive.length > 0) {
this.db.emit('address', this.id, receive);
this.emit('address', receive);
}
yield this.commit();
return receive;
});
@ -1349,17 +1344,8 @@ Wallet.prototype.getRedeem = co(function* getRedeem(hash) {
*/
Wallet.prototype.template = co(function* template(tx) {
var total = 0;
var i, rings, ring;
rings = yield this.deriveInputs(tx);
for (i = 0; i < rings.length; i++) {
ring = rings[i];
total += tx.template(ring);
}
return total;
var rings = yield this.deriveInputs(tx);
return tx.template(rings);
});
/**
@ -1372,7 +1358,7 @@ Wallet.prototype.template = co(function* template(tx) {
*/
Wallet.prototype.sign = co(function* sign(tx, options) {
var master, rings;
var rings;
if (!options)
options = {};
@ -1380,36 +1366,13 @@ Wallet.prototype.sign = co(function* sign(tx, options) {
if (typeof options === 'string' || Buffer.isBuffer(options))
options = { passphrase: options };
master = yield this.unlock(options.passphrase, options.timeout);
yield this.unlock(options.passphrase, options.timeout);
rings = yield this.deriveInputs(tx);
return yield this.signAsync(rings, tx);
return yield tx.signAsync(rings);
});
/**
* Sign a transaction asynchronously.
* @param {KeyRing[]} rings
* @param {MTX} tx
* @param {Function} callback - Returns [Error, Number] (total number
* of inputs scripts built and signed).
*/
Wallet.prototype.signAsync = function signAsync(rings, tx) {
var result;
if (!this.workerPool) {
try {
result = tx.sign(rings);
} catch (e) {
return Promise.reject(e);
}
return Promise.resolve(result);
}
return this.workerPool.sign(tx, rings, null);
};
/**
* Fill transaction with coins (accesses db).
* @param {TX} tx

View File

@ -128,7 +128,6 @@ function WalletDB(options) {
this.logger = options.logger || bcoin.defaultLogger;
this.batches = {};
this.wallets = {};
this.workerPool = null;
this.tip = this.network.genesis.hash;
this.height = 0;
@ -163,9 +162,6 @@ function WalletDB(options) {
bufferKeys: !utils.isBrowser
});
if (bcoin.useWorkers)
this.workerPool = new bcoin.workers();
this._init();
}
@ -184,13 +180,7 @@ WalletDB.layout = layout;
*/
WalletDB.prototype._init = function _init() {
var self = this;
if (bcoin.useWorkers) {
this.workerPool.on('error', function(err) {
self.emit('error', err);
});
}
;
};
/**
@ -249,7 +239,7 @@ WalletDB.prototype.backup = function backup(path) {
*/
WalletDB.prototype.getDepth = co(function* getDepth() {
var kv, iter, depth;
var result, iter, depth;
// This may seem like a strange way to do
// this, but updating a global state when
@ -266,14 +256,14 @@ WalletDB.prototype.getDepth = co(function* getDepth() {
reverse: true
});
kv = yield iter.next();
result = yield iter.next();
if (!kv)
if (!result)
return 1;
yield iter.end();
depth = layout.ww(kv[0]);
depth = layout.ww(result.key);
return depth + 1;
});
@ -1546,11 +1536,15 @@ PathInfo.prototype.fromTX = function fromTX(tx, table) {
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
paths = table[hash];
for (j = 0; j < paths.length; j++) {
path = paths[j];
if (path.wid !== this.wid)
continue;
this.pathMap[hash] = path;
if (!uniq[path.account]) {
uniq[path.account] = true;
this.accounts.push(path.account);
@ -1563,10 +1557,13 @@ PathInfo.prototype.fromTX = function fromTX(tx, table) {
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
paths = table[hash];
for (j = 0; j < paths.length; j++) {
path = paths[j];
if (path.wid !== this.wid)
continue;
this.paths.push(path);
}
}