diff --git a/Address.js b/Address.js index e429a31..f274333 100644 --- a/Address.js +++ b/Address.js @@ -1,22 +1,19 @@ -require('classtool'); +'use strict'; +var imports = require('soop').imports(); +var parent = imports.parent || require('./util/VersionedData'); -function ClassSpec(b) { - var superclass = b.superclass || require('./util/VersionedData').class(); - - function Address() { - Address.super(this, arguments); - } +function Address() { + Address.super(this, arguments); +} - Address.superclass = superclass; - superclass.applyEncodingsTo(Address); +Address.parent = parent; +parent.applyEncodingsTo(Address); - Address.prototype.validate = function() { - this.doAsBinary(function() { - Address.super(this, 'validate', arguments); - if(this.data.length !== 21) throw new Error('invalid data length'); - }); - }; +Address.prototype.validate = function() { + this.doAsBinary(function() { + Address.super(this, 'validate', arguments); + if(this.data.length !== 21) throw new Error('invalid data length'); + }); +}; - return Address; -} -module.defineClass(ClassSpec); +module.exports = require('soop')(Address); diff --git a/Block.js b/Block.js index c1f5238..aeda5f9 100644 --- a/Block.js +++ b/Block.js @@ -1,594 +1,591 @@ -require('classtool'); - -function spec(b) { - var util = b.util || require('./util/util'); - var Debug1 = b.Debug1 || function() {}; - var Script = b.Script || require('./Script').class(); - var Bignum = b.Bignum || require('bignum'); - var Binary = b.Binary || require('binary'); - var Step = b.Step || require('step'); - var buffertools = b.buffertools || require('buffertools'); - var Transaction = b.Transaction || require('./Transaction').class(); - var TransactionIn = Transaction.In; - var TransactionOut = Transaction.Out; - var COINBASE_OP = Transaction.COINBASE_OP; - var VerificationError = b.VerificationError || require('./util/error').VerificationError; - var BlockRules = { - maxTimeOffset: 2 * 60 * 60, // How far block timestamps can be into the future - largestHash: Bignum(2).pow(256) - }; +var imports = require('soop').imports(); + +var util = imports.util || require('./util/util'); +var Debug1 = imports.Debug1 || function() {}; +var Script = imports.Script || require('./Script'); +var Bignum = imports.Bignum || require('bignum'); +var Binary = imports.Binary || require('binary'); +var Step = imports.Step || require('step'); +var buffertools = imports.buffertools || require('buffertools'); +var Transaction = imports.Transaction || require('./Transaction'); +var TransactionIn = Transaction.In; +var TransactionOut = Transaction.Out; +var COINBASE_OP = Transaction.COINBASE_OP; +var VerificationError = imports.VerificationError || require('./util/error').VerificationError; +var BlockRules = { + maxTimeOffset: 2 * 60 * 60, // How far block timestamps can be into the future + largestHash: Bignum(2).pow(256) +}; - function Block(data) - { - if ("object" !== typeof data) { - data = {}; - } - this.hash = data.hash || null; - this.prev_hash = data.prev_hash || util.NULL_HASH; - this.merkle_root = data.merkle_root || util.NULL_HASH; - this.timestamp = data.timestamp || 0; - this.bits = data.bits || 0; - this.nonce = data.nonce || 0; - this.version = data.version || 0; - this.height = data.height || 0; - this.size = data.size || 0; - this.active = data.active || false; - this.chainWork = data.chainWork || util.EMPTY_BUFFER; - this.txs = data.txs || []; +function Block(data) +{ + if ("object" !== typeof data) { + data = {}; } + this.hash = data.hash || null; + this.prev_hash = data.prev_hash || util.NULL_HASH; + this.merkle_root = data.merkle_root || util.NULL_HASH; + this.timestamp = data.timestamp || 0; + this.bits = data.bits || 0; + this.nonce = data.nonce || 0; + this.version = data.version || 0; + this.height = data.height || 0; + this.size = data.size || 0; + this.active = data.active || false; + this.chainWork = data.chainWork || util.EMPTY_BUFFER; + this.txs = data.txs || []; +} + +Block.prototype.getHeader = function getHeader() { + var buf = new Buffer(80); + var ofs = 0; + buf.writeUInt32LE(this.version, ofs); ofs += 4; + this.prev_hash.copy(buf, ofs); ofs += 32; + this.merkle_root.copy(buf, ofs); ofs += 32; + buf.writeUInt32LE(this.timestamp, ofs); ofs += 4; + buf.writeUInt32LE(this.bits, ofs); ofs += 4; + buf.writeUInt32LE(this.nonce, ofs); ofs += 4; + return buf; +}; - Block.prototype.getHeader = function getHeader() { - var buf = new Buffer(80); - var ofs = 0; - buf.writeUInt32LE(this.version, ofs); ofs += 4; - this.prev_hash.copy(buf, ofs); ofs += 32; - this.merkle_root.copy(buf, ofs); ofs += 32; - buf.writeUInt32LE(this.timestamp, ofs); ofs += 4; - buf.writeUInt32LE(this.bits, ofs); ofs += 4; - buf.writeUInt32LE(this.nonce, ofs); ofs += 4; - return buf; - }; - - Block.prototype.parse = function parse(parser, headerOnly) { - this.version = parser.word32le(); - this.prev_hash = parser.buffer(32); - this.merkle_root = parser.buffer(32); - this.timestamp = parser.word32le(); - this.bits = parser.word32le(); - this.nonce = parser.word32le(); +Block.prototype.parse = function parse(parser, headerOnly) { + this.version = parser.word32le(); + this.prev_hash = parser.buffer(32); + this.merkle_root = parser.buffer(32); + this.timestamp = parser.word32le(); + this.bits = parser.word32le(); + this.nonce = parser.word32le(); - this.txs = []; - this.size = 0; + this.txs = []; + this.size = 0; - if (headerOnly) - return; + if (headerOnly) + return; - var txCount = parser.varInt(); + var txCount = parser.varInt(); - for (var i = 0; i < txCount; i++) { - var tx = new Transaction(); - tx.parse(parser); - this.txs.push(tx); - } - }; + for (var i = 0; i < txCount; i++) { + var tx = new Transaction(); + tx.parse(parser); + this.txs.push(tx); + } +}; - Block.prototype.calcHash = function calcHash() { - var header = this.getHeader(); +Block.prototype.calcHash = function calcHash() { + var header = this.getHeader(); - return util.twoSha256(header); - }; + return util.twoSha256(header); +}; - Block.prototype.checkHash = function checkHash() { - if (!this.hash || !this.hash.length) return false; - return buffertools.compare(this.calcHash(), this.hash) == 0; - }; +Block.prototype.checkHash = function checkHash() { + if (!this.hash || !this.hash.length) return false; + return buffertools.compare(this.calcHash(), this.hash) == 0; +}; - Block.prototype.getHash = function getHash() { - if (!this.hash || !this.hash.length) this.hash = this.calcHash(); +Block.prototype.getHash = function getHash() { + if (!this.hash || !this.hash.length) this.hash = this.calcHash(); - return this.hash; - }; + return this.hash; +}; - Block.prototype.checkProofOfWork = function checkProofOfWork() { - var target = util.decodeDiffBits(this.bits); +Block.prototype.checkProofOfWork = function checkProofOfWork() { + var target = util.decodeDiffBits(this.bits); - // TODO: Create a compare method in node-buffertools that uses the correct - // endian so we don't have to reverse both buffers before comparing. - buffertools.reverse(this.hash); + // TODO: Create a compare method in node-buffertools that uses the correct + // endian so we don't have to reverse both buffers before comparing. + buffertools.reverse(this.hash); - if (buffertools.compare(this.hash, target) > 0) { - throw new VerificationError('Difficulty target not met'); - } + if (buffertools.compare(this.hash, target) > 0) { + throw new VerificationError('Difficulty target not met'); + } - // Return the hash to its normal order - buffertools.reverse(this.hash); + // Return the hash to its normal order + buffertools.reverse(this.hash); - return true; - }; + return true; +}; - /** - * Returns the amount of work that went into this block. - * - * Work is defined as the average number of tries required to meet this - * block's difficulty target. For example a target that is greater than 5% - * of all possible hashes would mean that 20 "work" is required to meet it. - */ - Block.prototype.getWork = function getWork() { - var target = util.decodeDiffBits(this.bits, true); - return BlockRules.largestHash.div(target.add(1)); - }; +/** + * Returns the amount of work that went into this block. + * + * Work is defined as the average number of tries required to meet this + * block's difficulty target. For example a target that is greater than 5% + * of all possible hashes would mean that 20 "work" is required to meet it. + */ +Block.prototype.getWork = function getWork() { + var target = util.decodeDiffBits(this.bits, true); + return BlockRules.largestHash.div(target.add(1)); +}; - Block.prototype.checkTimestamp = function checkTimestamp() { - var currentTime = new Date().getTime() / 1000; - if (this.timestamp > currentTime + BlockRules.maxTimeOffset) { - throw new VerificationError('Timestamp too far into the future'); - } +Block.prototype.checkTimestamp = function checkTimestamp() { + var currentTime = new Date().getTime() / 1000; + if (this.timestamp > currentTime + BlockRules.maxTimeOffset) { + throw new VerificationError('Timestamp too far into the future'); + } - return true; - }; + return true; +}; - Block.prototype.checkTransactions = function checkTransactions(txs) { - if (!Array.isArray(txs) || txs.length <= 0) { - throw new VerificationError('No transactions'); - } - if (!txs[0].isCoinBase()) { - throw new VerificationError('First tx must be coinbase'); - } - for (var i = 1; i < txs.length; i++) { - if (txs[i].isCoinBase()) { - throw new VerificationError('Tx index '+i+' must not be coinbase'); - } +Block.prototype.checkTransactions = function checkTransactions(txs) { + if (!Array.isArray(txs) || txs.length <= 0) { + throw new VerificationError('No transactions'); + } + if (!txs[0].isCoinBase()) { + throw new VerificationError('First tx must be coinbase'); + } + for (var i = 1; i < txs.length; i++) { + if (txs[i].isCoinBase()) { + throw new VerificationError('Tx index '+i+' must not be coinbase'); } + } - return true; - }; - - /** - * Build merkle tree. - * - * Ported from Java. Original code: BitcoinJ by Mike Hearn - * Copyright (c) 2011 Google Inc. - */ - Block.prototype.getMerkleTree = function getMerkleTree(txs) { - // The merkle hash is based on a tree of hashes calculated from the transactions: - // - // merkleHash - // /\ - // / \ - // A B - // / \ / \ - // tx1 tx2 tx3 tx4 - // - // Basically transactions are hashed, then the hashes of the transactions are hashed - // again and so on upwards into the tree. The point of this scheme is to allow for - // disk space savings later on. - // - // This function is a direct translation of CBlock::BuildMerkleTree(). - - if (txs.length == 0) { - return [util.NULL_HASH.slice(0)]; - } + return true; +}; - // Start by adding all the hashes of the transactions as leaves of the tree. - var tree = txs.map(function (tx) { - return tx instanceof Transaction ? tx.getHash() : tx; - }); +/** + * Build merkle tree. + * + * Ported from Java. Original code: BitcoinJ by Mike Hearn + * Copyright (c) 2011 Google Inc. + */ +Block.prototype.getMerkleTree = function getMerkleTree(txs) { + // The merkle hash is based on a tree of hashes calculated from the transactions: + // + // merkleHash + // /\ + // / \ + // A B + // / \ / \ + // tx1 tx2 tx3 tx4 + // + // Basically transactions are hashed, then the hashes of the transactions are hashed + // again and so on upwards into the tree. The point of this scheme is to allow for + // disk space savings later on. + // + // This function is a direct translation of CBlock::BuildMerkleTree(). + + if (txs.length == 0) { + return [util.NULL_HASH.slice(0)]; + } - var j = 0; - // Now step through each level ... - for (var size = txs.length; size > 1; size = Math.floor((size + 1) / 2)) { - // and for each leaf on that level .. - for (var i = 0; i < size; i += 2) { - var i2 = Math.min(i + 1, size - 1); - var a = tree[j + i]; - var b = tree[j + i2]; - tree.push(util.twoSha256(Buffer.concat([a,b]))); - } - j += size; + // Start by adding all the hashes of the transactions as leaves of the tree. + var tree = txs.map(function (tx) { + return tx instanceof Transaction ? tx.getHash() : tx; + }); + + var j = 0; + // Now step through each level ... + for (var size = txs.length; size > 1; size = Math.floor((size + 1) / 2)) { + // and for each leaf on that level .. + for (var i = 0; i < size; i += 2) { + var i2 = Math.min(i + 1, size - 1); + var a = tree[j + i]; + var b = tree[j + i2]; + tree.push(util.twoSha256(Buffer.concat([a,b]))); } + j += size; + } - return tree; - }; + return tree; +}; - Block.prototype.calcMerkleRoot = function calcMerkleRoot(txs) { - var tree = this.getMerkleTree(txs); - return tree[tree.length - 1]; - }; +Block.prototype.calcMerkleRoot = function calcMerkleRoot(txs) { + var tree = this.getMerkleTree(txs); + return tree[tree.length - 1]; +}; - Block.prototype.checkMerkleRoot = function checkMerkleRoot(txs) { - if (!this.merkle_root || !this.merkle_root.length) { - throw new VerificationError('No merkle root'); - } +Block.prototype.checkMerkleRoot = function checkMerkleRoot(txs) { + if (!this.merkle_root || !this.merkle_root.length) { + throw new VerificationError('No merkle root'); + } - if (buffertools.compare(this.calcMerkleRoot(), this.merkle_root) == 0) { - throw new VerificationError('Merkle root incorrect'); - } + if (buffertools.compare(this.calcMerkleRoot(), this.merkle_root) == 0) { + throw new VerificationError('Merkle root incorrect'); + } - return true; - }; + return true; +}; - Block.prototype.checkBlock = function checkBlock(txs) { - if (!this.checkHash()) { - throw new VerificationError("Block hash invalid"); - } - this.checkProofOfWork(); - this.checkTimestamp(); +Block.prototype.checkBlock = function checkBlock(txs) { + if (!this.checkHash()) { + throw new VerificationError("Block hash invalid"); + } + this.checkProofOfWork(); + this.checkTimestamp(); - if (txs) { - this.checkTransactions(txs); - if (!this.checkMerkleRoot(txs)) { - throw new VerificationError("Merkle hash invalid"); - } + if (txs) { + this.checkTransactions(txs); + if (!this.checkMerkleRoot(txs)) { + throw new VerificationError("Merkle hash invalid"); } - return true; - }; + } + return true; +}; - Block.getBlockValue = function getBlockValue(height) { - var subsidy = Bignum(50).mul(util.COIN); - subsidy = subsidy.div(Bignum(2).pow(Math.floor(height / 210000))); - return subsidy; - }; +Block.getBlockValue = function getBlockValue(height) { + var subsidy = Bignum(50).mul(util.COIN); + subsidy = subsidy.div(Bignum(2).pow(Math.floor(height / 210000))); + return subsidy; +}; - Block.prototype.getBlockValue = function getBlockValue() { - return Block.getBlockValue(this.height); - }; +Block.prototype.getBlockValue = function getBlockValue() { + return Block.getBlockValue(this.height); +}; - Block.prototype.toString = function toString() { - return ""; - }; +Block.prototype.toString = function toString() { + return ""; +}; - /** - * Initializes some properties based on information from the parent block. - */ - Block.prototype.attachTo = function attachTo(parent) { - this.height = parent.height + 1; - this.setChainWork(parent.getChainWork().add(this.getWork())); - }; +/** + * Initializes some properties based on information from the parent block. + */ +Block.prototype.attachTo = function attachTo(parent) { + this.height = parent.height + 1; + this.setChainWork(parent.getChainWork().add(this.getWork())); +}; - Block.prototype.setChainWork = function setChainWork(chainWork) { - if (Buffer.isBuffer(chainWork)) { - // Nothing to do - } else if ("function" === typeof chainWork.toBuffer) { // duck-typing bignum - chainWork = chainWork.toBuffer(); - } else { - throw new Error("Block.setChainWork(): Invalid datatype"); - } +Block.prototype.setChainWork = function setChainWork(chainWork) { + if (Buffer.isBuffer(chainWork)) { + // Nothing to do + } else if ("function" === typeof chainWork.toBuffer) { // duck-typing bignum + chainWork = chainWork.toBuffer(); + } else { + throw new Error("Block.setChainWork(): Invalid datatype"); + } - this.chainWork = chainWork; - }; + this.chainWork = chainWork; +}; - Block.prototype.getChainWork = function getChainWork() { - return Bignum.fromBuffer(this.chainWork); - }; +Block.prototype.getChainWork = function getChainWork() { + return Bignum.fromBuffer(this.chainWork); +}; - /** - * Compares the chainWork of two blocks. - */ - Block.prototype.moreWorkThan = function moreWorkThan(otherBlock) { - return this.getChainWork().cmp(otherBlock.getChainWork()) > 0; - }; +/** + * Compares the chainWork of two blocks. + */ +Block.prototype.moreWorkThan = function moreWorkThan(otherBlock) { + return this.getChainWork().cmp(otherBlock.getChainWork()) > 0; +}; - /** - * Returns the difficulty target for the next block after this one. - */ - Block.prototype.getNextWork = - function getNextWork(blockChain, nextBlock, callback) { - var self = this; +/** + * Returns the difficulty target for the next block after this one. + */ +Block.prototype.getNextWork = +function getNextWork(blockChain, nextBlock, callback) { + var self = this; - var powLimit = blockChain.getMinDiff(); - var powLimitTarget = util.decodeDiffBits(powLimit, true); + var powLimit = blockChain.getMinDiff(); + var powLimitTarget = util.decodeDiffBits(powLimit, true); - var targetTimespan = blockChain.getTargetTimespan(); - var targetSpacing = blockChain.getTargetSpacing(); - var interval = targetTimespan / targetSpacing; + var targetTimespan = blockChain.getTargetTimespan(); + var targetSpacing = blockChain.getTargetSpacing(); + var interval = targetTimespan / targetSpacing; - if (this.height == 0) { - callback(null, this.bits); - } + if (this.height == 0) { + callback(null, this.bits); + } - if ((this.height+1) % interval !== 0) { - if (blockChain.isTestnet()) { - // Special testnet difficulty rules - var lastBlock = blockChain.getTopBlock(); + if ((this.height+1) % interval !== 0) { + if (blockChain.isTestnet()) { + // Special testnet difficulty rules + var lastBlock = blockChain.getTopBlock(); - // If the new block's timestamp is more than 2 * 10 minutes - // then allow mining of a min-difficulty block. - if (nextBlock.timestamp > this.timestamp + targetSpacing*2) { - callback(null, powLimit); + // If the new block's timestamp is more than 2 * 10 minutes + // then allow mining of a min-difficulty block. + if (nextBlock.timestamp > this.timestamp + targetSpacing*2) { + callback(null, powLimit); + } else { + // Return last non-"special-min-difficulty" block + if (this.bits != powLimit) { + // Current block is non-min-diff + callback(null, this.bits); } else { - // Return last non-"special-min-difficulty" block - if (this.bits != powLimit) { - // Current block is non-min-diff - callback(null, this.bits); - } else { - // Recurse backwards until a non min-diff block is found. - function lookForLastNonMinDiff(block, callback) { - try { - if (block.height > 0 && - block.height % interval !== 0 && - block.bits == powLimit) { - blockChain.getBlockByHeight( - block.height - 1, - function (err, lastBlock) { - try { - if (err) throw err; - lookForLastNonMinDiff(lastBlock, callback); - } catch (err) { - callback(err); - } + // Recurse backwards until a non min-diff block is found. + function lookForLastNonMinDiff(block, callback) { + try { + if (block.height > 0 && + block.height % interval !== 0 && + block.bits == powLimit) { + blockChain.getBlockByHeight( + block.height - 1, + function (err, lastBlock) { + try { + if (err) throw err; + lookForLastNonMinDiff(lastBlock, callback); + } catch (err) { + callback(err); } - ); - } else { - callback(null, block.bits); - } - } catch (err) { - callback(err); + } + ); + } else { + callback(null, block.bits); } - }; - lookForLastNonMinDiff(this, callback); - } + } catch (err) { + callback(err); + } + }; + lookForLastNonMinDiff(this, callback); } - } else { - // Not adjustment interval, next block has same difficulty - callback(null, this.bits); } } else { - // Get the first block from the old difficulty period - blockChain.getBlockByHeight( - this.height - interval + 1, - function (err, lastBlock) { - try { - if (err) throw err; - - // Determine how long the difficulty period really took - var actualTimespan = self.timestamp - lastBlock.timestamp; - - // There are some limits to how much we will adjust the difficulty in - // one step - if (actualTimespan < targetTimespan/4) { - actualTimespan = targetTimespan/4; - } - if (actualTimespan > targetTimespan*4) { - actualTimespan = targetTimespan*4; - } + // Not adjustment interval, next block has same difficulty + callback(null, this.bits); + } + } else { + // Get the first block from the old difficulty period + blockChain.getBlockByHeight( + this.height - interval + 1, + function (err, lastBlock) { + try { + if (err) throw err; + + // Determine how long the difficulty period really took + var actualTimespan = self.timestamp - lastBlock.timestamp; + + // There are some limits to how much we will adjust the difficulty in + // one step + if (actualTimespan < targetTimespan/4) { + actualTimespan = targetTimespan/4; + } + if (actualTimespan > targetTimespan*4) { + actualTimespan = targetTimespan*4; + } - var oldTarget = util.decodeDiffBits(self.bits, true); - var newTarget = oldTarget.mul(actualTimespan).div(targetTimespan); + var oldTarget = util.decodeDiffBits(self.bits, true); + var newTarget = oldTarget.mul(actualTimespan).div(targetTimespan); - if (newTarget.cmp(powLimitTarget) > 0) { - newTarget = powLimitTarget; - } + if (newTarget.cmp(powLimitTarget) > 0) { + newTarget = powLimitTarget; + } - Debug1('Difficulty retarget (target='+targetTimespan + - ', actual='+actualTimespan+')'); - Debug1('Before: '+oldTarget.toBuffer().toString('hex')); - Debug1('After: '+newTarget.toBuffer().toString('hex')); + Debug1('Difficulty retarget (target='+targetTimespan + + ', actual='+actualTimespan+')'); + Debug1('Before: '+oldTarget.toBuffer().toString('hex')); + Debug1('After: '+newTarget.toBuffer().toString('hex')); - callback(null, util.encodeDiffBits(newTarget)); - } catch (err) { - callback(err); - } + callback(null, util.encodeDiffBits(newTarget)); + } catch (err) { + callback(err); } - ); - } - }; + } + ); + } +}; - var medianTimeSpan = 11; +var medianTimeSpan = 11; - Block.prototype.getMedianTimePast = - function getMedianTimePast(blockChain, callback) - { - var self = this; +Block.prototype.getMedianTimePast = +function getMedianTimePast(blockChain, callback) +{ + var self = this; - Step( - function getBlocks() { - var heights = []; - for (var i = 0, m = medianTimeSpan; i < m && (self.height - i) >= 0; i++) { - heights.push(self.height - i); + Step( + function getBlocks() { + var heights = []; + for (var i = 0, m = medianTimeSpan; i < m && (self.height - i) >= 0; i++) { + heights.push(self.height - i); + } + blockChain.getBlocksByHeights(heights, this); + }, + function calcMedian(err, blocks) { + if (err) throw err; + + var timestamps = blocks.map(function (block) { + if (!block) { + throw new Error("Prior block missing, cannot calculate median time"); } - blockChain.getBlocksByHeights(heights, this); - }, - function calcMedian(err, blocks) { - if (err) throw err; - - var timestamps = blocks.map(function (block) { - if (!block) { - throw new Error("Prior block missing, cannot calculate median time"); - } - return +block.timestamp; - }); + return +block.timestamp; + }); - // Sort timestamps - timestamps = timestamps.sort(); + // Sort timestamps + timestamps = timestamps.sort(); - // Return median timestamp - this(null, timestamps[Math.floor(timestamps.length/2)]); - }, - callback - ); - }; + // Return median timestamp + this(null, timestamps[Math.floor(timestamps.length/2)]); + }, + callback + ); +}; - Block.prototype.verifyChild = - function verifyChild(blockChain, child, callback) - { - var self = this; - - Step( - function getExpectedDifficulty() { - self.getNextWork(blockChain, child, this); - }, - function verifyExpectedDifficulty(err, nextWork) { - if (err) throw err; - - if (+child.bits !== +nextWork) { - throw new VerificationError("Incorrect proof of work '"+child.bits+"',"+ - " should be '"+nextWork+"'."); - } +Block.prototype.verifyChild = +function verifyChild(blockChain, child, callback) +{ + var self = this; + + Step( + function getExpectedDifficulty() { + self.getNextWork(blockChain, child, this); + }, + function verifyExpectedDifficulty(err, nextWork) { + if (err) throw err; + + if (+child.bits !== +nextWork) { + throw new VerificationError("Incorrect proof of work '"+child.bits+"',"+ + " should be '"+nextWork+"'."); + } - this(); - }, - function getMinimumTimestamp(err) { - if (err) throw err; + this(); + }, + function getMinimumTimestamp(err) { + if (err) throw err; - self.getMedianTimePast(blockChain, this); - }, - function verifyTimestamp(err, medianTimePast) { - if (err) throw err; + self.getMedianTimePast(blockChain, this); + }, + function verifyTimestamp(err, medianTimePast) { + if (err) throw err; - if (child.timestamp <= medianTimePast) { - throw new VerificationError("Block's timestamp is too early"); - } + if (child.timestamp <= medianTimePast) { + throw new VerificationError("Block's timestamp is too early"); + } - this(); - }, - callback - ); - }; + this(); + }, + callback + ); +}; - Block.prototype.createCoinbaseTx = - function createCoinbaseTx(beneficiary) - { - var tx = new Transaction(); - tx.ins.push(new TransactionIn({ - s: util.EMPTY_BUFFER, - q: 0xffffffff, - o: COINBASE_OP - })); - tx.outs.push(new TransactionOut({ - v: util.bigIntToValue(this.getBlockValue()), - s: Script.createPubKeyOut(beneficiary).getBuffer() - })); - return tx; - }; +Block.prototype.createCoinbaseTx = +function createCoinbaseTx(beneficiary) +{ + var tx = new Transaction(); + tx.ins.push(new TransactionIn({ + s: util.EMPTY_BUFFER, + q: 0xffffffff, + o: COINBASE_OP + })); + tx.outs.push(new TransactionOut({ + v: util.bigIntToValue(this.getBlockValue()), + s: Script.createPubKeyOut(beneficiary).getBuffer() + })); + return tx; +}; - Block.prototype.prepareNextBlock = - function prepareNextBlock(blockChain, beneficiary, time, callback) - { - var self = this; +Block.prototype.prepareNextBlock = +function prepareNextBlock(blockChain, beneficiary, time, callback) +{ + var self = this; - var newBlock = new Block(); - Step( - function getMedianTimePastStep() { - self.getMedianTimePast(blockChain, this); - }, + var newBlock = new Block(); + Step( + function getMedianTimePastStep() { + self.getMedianTimePast(blockChain, this); + }, - function getNextWorkStep(err, medianTimePast) { - if (err) throw err; + function getNextWorkStep(err, medianTimePast) { + if (err) throw err; - if (!time) { - // TODO: Use getAdjustedTime for the second timestamp - time = Math.max(medianTimePast+1, - Math.floor(new Date().getTime() / 1000)); - } + if (!time) { + // TODO: Use getAdjustedTime for the second timestamp + time = Math.max(medianTimePast+1, + Math.floor(new Date().getTime() / 1000)); + } - self.getNextWork(blockChain, newBlock, this); - }, + self.getNextWork(blockChain, newBlock, this); + }, - function applyNextWorkStep(err, nextWork) { - if (err) throw err; - newBlock.bits = nextWork; - this(null); - }, + function applyNextWorkStep(err, nextWork) { + if (err) throw err; + newBlock.bits = nextWork; + this(null); + }, - function miscStep(err) { - if (err) throw err; + function miscStep(err) { + if (err) throw err; - newBlock.version = 1; - newBlock.timestamp = time; - newBlock.prev_hash = self.getHash().slice(0); - newBlock.height = self.height+1; + newBlock.version = 1; + newBlock.timestamp = time; + newBlock.prev_hash = self.getHash().slice(0); + newBlock.height = self.height+1; - // Create coinbase transaction - var txs = []; + // Create coinbase transaction + var txs = []; - var tx = newBlock.createCoinbaseTx(beneficiary); - txs.push(tx); + var tx = newBlock.createCoinbaseTx(beneficiary); + txs.push(tx); - newBlock.merkle_root = newBlock.calcMerkleRoot(txs); + newBlock.merkle_root = newBlock.calcMerkleRoot(txs); - // Return reference to (unfinished) block - this(null, {block: newBlock, txs: txs}); - }, - callback - ); - }; + // Return reference to (unfinished) block + this(null, {block: newBlock, txs: txs}); + }, + callback + ); +}; - Block.prototype.mineNextBlock = - function mineNextBlock(blockChain, beneficiary, time, miner, callback) - { - this.prepareNextBlock(blockChain, beneficiary, time, function (err, data) { - try { - if (err) throw err; +Block.prototype.mineNextBlock = +function mineNextBlock(blockChain, beneficiary, time, miner, callback) +{ + this.prepareNextBlock(blockChain, beneficiary, time, function (err, data) { + try { + if (err) throw err; - var newBlock = data.block; - var txs = data.txs; + var newBlock = data.block; + var txs = data.txs; - newBlock.solve(miner, function (err, nonce) { - newBlock.nonce = nonce; + newBlock.solve(miner, function (err, nonce) { + newBlock.nonce = nonce; - // Make sure hash is cached - newBlock.getHash(); + // Make sure hash is cached + newBlock.getHash(); - callback(err, newBlock, txs); - }); + callback(err, newBlock, txs); + }); - // Return reference to (unfinished) block - return newBlock; - } catch (e) { - callback(e); - } - }); - }; + // Return reference to (unfinished) block + return newBlock; + } catch (e) { + callback(e); + } + }); +}; + +Block.prototype.solve = function solve(miner, callback) { + var header = this.getHeader(); + var target = util.decodeDiffBits(this.bits); + miner.solve(header, target, callback); +}; - Block.prototype.solve = function solve(miner, callback) { - var header = this.getHeader(); - var target = util.decodeDiffBits(this.bits); - miner.solve(header, target, callback); +/** + * Returns an object with the same field names as jgarzik's getblock patch. + */ +Block.prototype.getStandardizedObject = +function getStandardizedObject(txs) +{ + var block = { + hash: util.formatHashFull(this.getHash()), + version: this.version, + prev_block: util.formatHashFull(this.prev_hash), + mrkl_root: util.formatHashFull(this.merkle_root), + time: this.timestamp, + bits: this.bits, + nonce: this.nonce, + height: this.height }; - /** - * Returns an object with the same field names as jgarzik's getblock patch. - */ - Block.prototype.getStandardizedObject = - function getStandardizedObject(txs) - { - var block = { - hash: util.formatHashFull(this.getHash()), - version: this.version, - prev_block: util.formatHashFull(this.prev_hash), - mrkl_root: util.formatHashFull(this.merkle_root), - time: this.timestamp, - bits: this.bits, - nonce: this.nonce, - height: this.height - }; - - - if (txs) { - var mrkl_tree = this.getMerkleTree(txs).map(function (buffer) { - return util.formatHashFull(buffer); - }); - block.mrkl_root = mrkl_tree[mrkl_tree.length - 1]; - - block.n_tx = txs.length; - var totalSize = 80; // Block header - totalSize += util.getVarIntSize(txs.length); // txn_count - txs = txs.map(function (tx) { - tx = tx.getStandardizedObject(); - totalSize += tx.size; - return tx; - }); - block.size = totalSize; - block.tx = txs; - block.mrkl_tree = mrkl_tree; - } else { - block.size = this.size; - } - return block; - }; + if (txs) { + var mrkl_tree = this.getMerkleTree(txs).map(function (buffer) { + return util.formatHashFull(buffer); + }); + block.mrkl_root = mrkl_tree[mrkl_tree.length - 1]; + + block.n_tx = txs.length; + var totalSize = 80; // Block header + totalSize += util.getVarIntSize(txs.length); // txn_count + txs = txs.map(function (tx) { + tx = tx.getStandardizedObject(); + totalSize += tx.size; + return tx; + }); + block.size = totalSize; + block.tx = txs; - return Block; + block.mrkl_tree = mrkl_tree; + } else { + block.size = this.size; + } + return block; }; -module.defineClass(spec); + +module.exports = require('soop')(Block); diff --git a/Bloom.js b/Bloom.js index d514053..0bb166b 100644 --- a/Bloom.js +++ b/Bloom.js @@ -1,116 +1,111 @@ -require('classtool'); - -function ClassSpec(b) { - var MAX_BLOOM_FILTER_SIZE = 36000; // bytes - var MAX_HASH_FUNCS = 50; - var LN2SQUARED = 0.4804530139182014246671025263266649717305529515945455; - var LN2 = 0.6931471805599453094172321214581765680755001343602552; - var bit_mask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]; - - function Bloom() { - this.data = ''; - this.hashFuncs = 0; - }; - - function ROTL32(x, r) { - return (x << r) | (x >> (32 - r)); - }; - - function getBlockU32(blockIdx, data) { - var idx = blockIdx * 4; - var v = (data[idx + 0] << (0 * 8)) | - (data[idx + 1] << (1 * 8)) | - (data[idx + 2] << (2 * 8)) | - (data[idx + 3] << (3 * 8)); - return v; - }; - - Bloom.prototype.hash = function(hashNum, data) { - var h1 = hashNum * (0xffffffff / (this.hashFuncs - 1)); - var c1 = 0xcc9e2d51; - var c2 = 0x1b873593; - var nBlocks = data.length / 4; - - // data body - for (var i = -nBlocks; i; i++) { - var k1 = getBlockU32(i); - - k1 *= c1; - k1 = ROTLF32(k1, 15); - k1 *= c2; - - h1 ^= k1; - h1 = ROTFL(h1, 13); - h1 = h1 * 5 + 0xe6546b64; - } - - // tail (trailing 1-3 bytes) - var tail = data.slice(nBlocks * 4); - - var k1 = 0; - - switch (data.length & 3) { - case 3: k1 ^= tail[2] << 16; - case 2: k1 ^= tail[1] << 8; - case 1: k1 ^= tail[0]; - k1 *= c1; - k1 = ROTL32(k1, 15); - k1 *= c2; - h1 ^= k1; - } - - // finalize - h1 ^= data.length; - h1 ^= h1 >> 16; - h1 *= 0x85ebca6b; - h1 ^= h1 >> 13; - h1 *= 0xc2b2ae35; - h1 ^= h1 >> 16; - - return h1 % (this.data.length * 8); - }; - - Bloom.prototype.insert = function(data) { - for (var i = 0; i < this.hashFuncs; i++) { - var index = this.hash(i, data); - this.data[index >> 3] |= bit_mask[7 & index]; - } - }; - - Bloom.prototype.contains = function(data) { - for (var i = 0; i < this.hashFuncs; i++) { - var index = this.hash(i, data); - if (!(this.data[index >> 3] & bit_mask[7 & index])) - return false; - } - - return true; - }; - - Bloom.prototype.sizeOk = function() { - return this.data.length <= MAX_BLOOM_FILTER_SIZE && - this.hashFuncs <= MAX_HASH_FUNCS; - }; - - function toInt(v) { - return ~~v; +var MAX_BLOOM_FILTER_SIZE = 36000; // bytes +var MAX_HASH_FUNCS = 50; +var LN2SQUARED = 0.4804530139182014246671025263266649717305529515945455; +var LN2 = 0.6931471805599453094172321214581765680755001343602552; +var bit_mask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]; + +function Bloom() { + this.data = ''; + this.hashFuncs = 0; +}; + +function ROTL32(x, r) { + return (x << r) | (x >> (32 - r)); +}; + +function getBlockU32(blockIdx, data) { + var idx = blockIdx * 4; + var v = (data[idx + 0] << (0 * 8)) | + (data[idx + 1] << (1 * 8)) | + (data[idx + 2] << (2 * 8)) | + (data[idx + 3] << (3 * 8)); + return v; +}; + +Bloom.prototype.hash = function(hashNum, data) { + var h1 = hashNum * (0xffffffff / (this.hashFuncs - 1)); + var c1 = 0xcc9e2d51; + var c2 = 0x1b873593; + var nBlocks = data.length / 4; + + // data body + for (var i = -nBlocks; i; i++) { + var k1 = getBlockU32(i); + + k1 *= c1; + k1 = ROTLF32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTFL(h1, 13); + h1 = h1 * 5 + 0xe6546b64; } - function min(a, b) { - if (a < b) - return a; - return b; + // tail (trailing 1-3 bytes) + var tail = data.slice(nBlocks * 4); + + var k1 = 0; + + switch (data.length & 3) { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; + k1 = ROTL32(k1, 15); + k1 *= c2; + h1 ^= k1; } - Bloom.prototype.init = function(elements, FPRate) { - var filterSize = min(toInt(-1.0 / LN2SQUARED * elements * Math.log(FPRate)), - MAX_BLOOM_FILTER_SIZE * 8) / 8; - this.data[filterSize] = 0; - this.hashFuncs = min(toInt(this.data.length * 8 / elements * LN2), - MAX_HASH_FUNCS); - }; + // finalize + h1 ^= data.length; + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; - return Bloom; + return h1 % (this.data.length * 8); }; -module.defineClass(ClassSpec); +Bloom.prototype.insert = function(data) { + for (var i = 0; i < this.hashFuncs; i++) { + var index = this.hash(i, data); + this.data[index >> 3] |= bit_mask[7 & index]; + } +}; + +Bloom.prototype.contains = function(data) { + for (var i = 0; i < this.hashFuncs; i++) { + var index = this.hash(i, data); + if (!(this.data[index >> 3] & bit_mask[7 & index])) + return false; + } + + return true; +}; + +Bloom.prototype.sizeOk = function() { + return this.data.length <= MAX_BLOOM_FILTER_SIZE && + this.hashFuncs <= MAX_HASH_FUNCS; +}; + +function toInt(v) { + return ~~v; +} + +function min(a, b) { + if (a < b) + return a; + return b; +} + +Bloom.prototype.init = function(elements, FPRate) { + var filterSize = min(toInt(-1.0 / LN2SQUARED * elements * Math.log(FPRate)), + MAX_BLOOM_FILTER_SIZE * 8) / 8; + this.data[filterSize] = 0; + this.hashFuncs = min(toInt(this.data.length * 8 / elements * LN2), + MAX_HASH_FUNCS); +}; + + +module.exports = require('soop')(Bloom); diff --git a/Connection.js b/Connection.js index 4672d16..f273ec3 100644 --- a/Connection.js +++ b/Connection.js @@ -1,547 +1,544 @@ -require('classtool'); - -function spec(b) { - var config = b.config || require('./config'); - var log = b.log || require('./util/log'); - var network = b.network || require('./networks')[config.network]; - - var MAX_RECEIVE_BUFFER = 10000000; - var PROTOCOL_VERSION = 70000; - - var Binary = b.Binary || require('binary'); - var Put = b.Put || require('bufferput'); - var Buffers = b.Buffers || require('buffers'); - require('./Buffers.monkey').patch(Buffers); - var noop = function() {}; - var Block = require('./Block').class(); - var Transaction = require('./Transaction').class(); - var util = b.util || require('./util/util'); - var Parser = b.Parser || require('./util/BinaryParser').class(); - var buffertools = b.buffertools || require('buffertools'); - var doubleSha256 = b.doubleSha256 || util.twoSha256; - var nonce = util.generateNonce(); - - var BIP0031_VERSION = 60000; - - function Connection(socket, peer) { - Connection.super(this, arguments); - this.socket = socket; - this.peer = peer; - - // A connection is considered "active" once we have received verack - this.active = false; - // The version incoming packages are interpreted as - this.recvVer = 0; - // The version outgoing packages are sent as - this.sendVer = 0; - // The (claimed) height of the remote peer's block chain - this.bestHeight = 0; - // Is this an inbound connection? - this.inbound = !!socket.server; - // Have we sent a getaddr on this connection? - this.getaddr = false; - - // Receive buffer - this.buffers = new Buffers(); - - // Starting 20 Feb 2012, Version 0.2 is obsolete - // This is the same behavior as the official client - if (new Date().getTime() > 1329696000000) { - this.recvVer = 209; - this.sendVer = 209; - } - - this.setupHandlers(); +var imports = require('soop').imports(); + +var config = imports.config || require('./config'); +var log = imports.log || require('./util/log'); +var network = imports.network || require('./networks')[config.network]; + +var MAX_RECEIVE_BUFFER = 10000000; +var PROTOCOL_VERSION = 70000; + +var Binary = imports.Binary || require('binary'); +var Put = imports.Put || require('bufferput'); +var Buffers = imports.Buffers || require('buffers'); +require('./Buffers.monkey').patch(Buffers); + +var Block = require('./Block'); +var Transaction = require('./Transaction'); +var util = imports.util || require('./util/util'); +var Parser = imports.Parser || require('./util/BinaryParser'); +var buffertools = imports.buffertools || require('buffertools'); +var doubleSha256 = imports.doubleSha256 || util.twoSha256; +var nonce = util.generateNonce(); + +var BIP0031_VERSION = 60000; + +function Connection(socket, peer) { + Connection.super(this, arguments); + this.socket = socket; + this.peer = peer; + + // A connection is considered "active" once we have received verack + this.active = false; + // The version incoming packages are interpreted as + this.recvVer = 0; + // The version outgoing packages are sent as + this.sendVer = 0; + // The (claimed) height of the remote peer's block chain + this.bestHeight = 0; + // Is this an inbound connection? + this.inbound = !!socket.server; + // Have we sent a getaddr on this connection? + this.getaddr = false; + + // Receive buffer + this.buffers = new Buffers(); + + // Starting 20 Feb 2012, Version 0.2 is obsolete + // This is the same behavior as the official client + if (new Date().getTime() > 1329696000000) { + this.recvVer = 209; + this.sendVer = 209; } - Connection.superclass = b.superclass || require('events').EventEmitter; - - Connection.prototype.setupHandlers = function () { - this.socket.addListener('connect', this.handleConnect.bind(this)); - this.socket.addListener('error', this.handleError.bind(this)); - this.socket.addListener('end', this.handleDisconnect.bind(this)); - this.socket.addListener('data', (function (data) { - var dumpLen = 35; - log.debug('['+this.peer+'] '+ - 'Recieved '+data.length+' bytes of data:'); - log.debug('... '+ buffertools.toHex(data.slice(0, dumpLen > data.length ? - data.length : dumpLen)) + - (data.length > dumpLen ? '...' : '')); - }).bind(this)); - this.socket.addListener('data', this.handleData.bind(this)); - }; - Connection.prototype.handleConnect = function () { - if (!this.inbound) { - this.sendVersion(); - } - this.emit('connect', { - conn: this, - socket: this.socket, - peer: this.peer - }); - }; - - Connection.prototype.handleError = function(err) { - if (err.errno == 110 || err.errno == 'ETIMEDOUT') { - log.info('connection timed out for '+this.peer); - } else if (err.errno == 111 || err.errno == 'ECONNREFUSED') { - log.info('connection refused for '+this.peer); - } else { - log.warn('connection with '+this.peer+' '+err.toString()); - } - this.emit('error', { - conn: this, - socket: this.socket, - peer: this.peer, - err: err - }); - }; + this.setupHandlers(); +} +Connection.parent = imports.parent || require('events').EventEmitter; + +Connection.prototype.setupHandlers = function () { + this.socket.addListener('connect', this.handleConnect.bind(this)); + this.socket.addListener('error', this.handleError.bind(this)); + this.socket.addListener('end', this.handleDisconnect.bind(this)); + this.socket.addListener('data', (function (data) { + var dumpLen = 35; + log.debug('['+this.peer+'] '+ + 'Recieved '+data.length+' bytes of data:'); + log.debug('... '+ buffertools.toHex(data.slice(0, dumpLen > data.length ? + data.length : dumpLen)) + + (data.length > dumpLen ? '...' : '')); + }).bind(this)); + this.socket.addListener('data', this.handleData.bind(this)); +}; - Connection.prototype.handleDisconnect = function () { - this.emit('disconnect', { - conn: this, - socket: this.socket, - peer: this.peer - }); - }; +Connection.prototype.handleConnect = function () { + if (!this.inbound) { + this.sendVersion(); + } + this.emit('connect', { + conn: this, + socket: this.socket, + peer: this.peer + }); +}; - Connection.prototype.handleMessage = function(message) { - if (!message) { - // Parser was unable to make sense of the message, drop it - return; - } +Connection.prototype.handleError = function(err) { + if (err.errno == 110 || err.errno == 'ETIMEDOUT') { + log.info('connection timed out for '+this.peer); + } else if (err.errno == 111 || err.errno == 'ECONNREFUSED') { + log.info('connection refused for '+this.peer); + } else { + log.warn('connection with '+this.peer+' '+err.toString()); + } + this.emit('error', { + conn: this, + socket: this.socket, + peer: this.peer, + err: err + }); +}; - try { - switch (message.command) { - case 'version': - // Did we connect to ourself? - if (buffertools.compare(nonce, message.nonce) === 0) { - this.socket.end(); - return; - } - - if (this.inbound) { - this.sendVersion(); - } - - if (message.version >= 209) { - this.sendMessage('verack', new Buffer([])); - } - this.sendVer = Math.min(message.version, PROTOCOL_VERSION); - if (message.version < 209) { - this.recvVer = Math.min(message.version, PROTOCOL_VERSION); - } else { - // We won't start expecting a checksum until after we've received - // the "verack" message. - this.once('verack', (function () { - this.recvVer = message.version; - }).bind(this)); - } - this.bestHeight = message.start_height; - break; - - case 'verack': - this.recvVer = Math.min(message.version, PROTOCOL_VERSION); - this.active = true; - break; - - case 'ping': - if ("object" === typeof message.nonce) { - this.sendPong(message.nonce); - } - break; - } - } catch (e) { - log.err('Error while handling "'+message.command+'" message from ' + - this.peer + ':\n' + - (e.stack ? e.stack : e.toString())); - return; - } - this.emit(message.command, { - conn: this, - socket: this.socket, - peer: this.peer, - message: message - }); - }; +Connection.prototype.handleDisconnect = function () { + this.emit('disconnect', { + conn: this, + socket: this.socket, + peer: this.peer + }); +}; - Connection.prototype.sendPong = function (nonce) { - this.sendMessage('pong', nonce); - }; +Connection.prototype.handleMessage = function(message) { + if (!message) { + // Parser was unable to make sense of the message, drop it + return; + } - Connection.prototype.sendVersion = function () { - var subversion = '/BitcoinX:0.1/'; - - var put = new Put(); - put.word32le(PROTOCOL_VERSION); // version - put.word64le(1); // services - put.word64le(Math.round(new Date().getTime()/1000)); // timestamp - put.pad(26); // addr_me - put.pad(26); // addr_you - put.put(nonce); - put.varint(subversion.length); - put.put(new Buffer(subversion, 'ascii')); - put.word32le(0); - - this.sendMessage('version', put.buffer()); - }; + try { + switch (message.command) { + case 'version': + // Did we connect to ourself? + if (buffertools.compare(nonce, message.nonce) === 0) { + this.socket.end(); + return; + } - Connection.prototype.sendGetBlocks = function (starts, stop, wantHeaders) { - var put = new Put(); - put.word32le(this.sendVer); + if (this.inbound) { + this.sendVersion(); + } - put.varint(starts.length); - for (var i = 0; i < starts.length; i++) { - if (starts[i].length != 32) { - throw new Error('Invalid hash length'); + if (message.version >= 209) { + this.sendMessage('verack', new Buffer([])); } + this.sendVer = Math.min(message.version, PROTOCOL_VERSION); + if (message.version < 209) { + this.recvVer = Math.min(message.version, PROTOCOL_VERSION); + } else { + // We won't start expecting a checksum until after we've received + // the "verack" message. + this.once('verack', (function () { + this.recvVer = message.version; + }).bind(this)); + } + this.bestHeight = message.start_height; + break; - put.put(starts[i]); - } + case 'verack': + this.recvVer = Math.min(message.version, PROTOCOL_VERSION); + this.active = true; + break; - var stopBuffer = new Buffer(stop, 'binary'); - if (stopBuffer.length != 32) { - throw new Error('Invalid hash length'); + case 'ping': + if ("object" === typeof message.nonce) { + this.sendPong(message.nonce); + } + break; } + } catch (e) { + log.err('Error while handling "'+message.command+'" message from ' + + this.peer + ':\n' + + (e.stack ? e.stack : e.toString())); + return; + } + this.emit(message.command, { + conn: this, + socket: this.socket, + peer: this.peer, + message: message + }); +}; - put.put(stopBuffer); +Connection.prototype.sendPong = function (nonce) { + this.sendMessage('pong', nonce); +}; - var command = 'getblocks'; - if (wantHeaders) - command = 'getheaders'; - this.sendMessage(command, put.buffer()); - }; +Connection.prototype.sendVersion = function () { + var subversion = '/BitcoinX:0.1/'; + + var put = new Put(); + put.word32le(PROTOCOL_VERSION); // version + put.word64le(1); // services + put.word64le(Math.round(new Date().getTime()/1000)); // timestamp + put.pad(26); // addr_me + put.pad(26); // addr_you + put.put(nonce); + put.varint(subversion.length); + put.put(new Buffer(subversion, 'ascii')); + put.word32le(0); + + this.sendMessage('version', put.buffer()); +}; - Connection.prototype.sendGetHeaders = function(starts, stop) { - this.sendGetBlocks(starts, stop, true); - }; +Connection.prototype.sendGetBlocks = function (starts, stop, wantHeaders) { + var put = new Put(); + put.word32le(this.sendVer); - Connection.prototype.sendGetData = function (invs) { - var put = new Put(); - put.varint(invs.length); - for (var i = 0; i < invs.length; i++) { - put.word32le(invs[i].type); - put.put(invs[i].hash); + put.varint(starts.length); + for (var i = 0; i < starts.length; i++) { + if (starts[i].length != 32) { + throw new Error('Invalid hash length'); } - this.sendMessage('getdata', put.buffer()); - }; - - Connection.prototype.sendGetAddr = function (invs) { - var put = new Put(); - this.sendMessage('getaddr', put.buffer()); - }; - Connection.prototype.sendInv = function(data) { - if(!Array.isArray(data)) data = [data]; - var put = new Put(); - put.varint(data.length); - data.forEach(function (value) { - if (value instanceof Block) { - // Block - put.word32le(2); // MSG_BLOCK - } else { - // Transaction - put.word32le(1); // MSG_TX - } - put.put(value.getHash()); - }); - this.sendMessage('inv', put.buffer()); - }; + put.put(starts[i]); + } - Connection.prototype.sendHeaders = function (headers) { - var put = new Put(); - put.varint(headers.length); - headers.forEach(function (header) { - put.put(header); + var stopBuffer = new Buffer(stop, 'binary'); + if (stopBuffer.length != 32) { + throw new Error('Invalid hash length'); + } - // Indicate 0 transactions - put.word8(0); - }); - this.sendMessage('headers', put.buffer()); - }; + put.put(stopBuffer); - Connection.prototype.sendTx = function (tx) { - this.sendMessage('tx', tx.serialize()); - }; + var command = 'getblocks'; + if (wantHeaders) + command = 'getheaders'; + this.sendMessage(command, put.buffer()); +}; - Connection.prototype.sendBlock = function (block, txs) { - var put = new Put(); +Connection.prototype.sendGetHeaders = function(starts, stop) { + this.sendGetBlocks(starts, stop, true); +}; - // Block header - put.put(block.getHeader()); +Connection.prototype.sendGetData = function (invs) { + var put = new Put(); + put.varint(invs.length); + for (var i = 0; i < invs.length; i++) { + put.word32le(invs[i].type); + put.put(invs[i].hash); + } + this.sendMessage('getdata', put.buffer()); +}; - // List of transactions - put.varint(txs.length); - txs.forEach(function (tx) { - put.put(tx.serialize()); - }); +Connection.prototype.sendGetAddr = function (invs) { + var put = new Put(); + this.sendMessage('getaddr', put.buffer()); +}; - this.sendMessage('block', put.buffer()); - }; +Connection.prototype.sendInv = function(data) { + if(!Array.isArray(data)) data = [data]; + var put = new Put(); + put.varint(data.length); + data.forEach(function (value) { + if (value instanceof Block) { + // Block + put.word32le(2); // MSG_BLOCK + } else { + // Transaction + put.word32le(1); // MSG_TX + } + put.put(value.getHash()); + }); + this.sendMessage('inv', put.buffer()); +}; - Connection.prototype.sendMessage = function (command, payload) { - try { - var magic = network.magic; - var commandBuf = new Buffer(command, 'ascii'); - if (commandBuf.length > 12) throw 'Command name too long'; +Connection.prototype.sendHeaders = function (headers) { + var put = new Put(); + put.varint(headers.length); + headers.forEach(function (header) { + put.put(header); - var checksum; - if (this.sendVer >= 209) { - checksum = doubleSha256(payload).slice(0, 4); - } else { - checksum = new Buffer([]); - } + // Indicate 0 transactions + put.word8(0); + }); + this.sendMessage('headers', put.buffer()); +}; - var message = new Put(); // -- HEADER -- - message.put(magic); // magic bytes - message.put(commandBuf); // command name - message.pad(12 - commandBuf.length); // zero-padded - message.word32le(payload.length); // payload length - message.put(checksum); // checksum - // -- BODY -- - message.put(payload); // payload data - - var buffer = message.buffer(); - - log.debug('['+this.peer+'] '+ - "Sending message "+command+" ("+payload.length+" bytes)"); - - this.socket.write(buffer); - } catch (err) { - // TODO: We should catch this error one level higher in order to better - // determine how to react to it. For now though, ignoring it will do. - log.err("Error while sending message to peer "+this.peer+": "+ - (err.stack ? err.stack : err.toString())); - } - }; +Connection.prototype.sendTx = function (tx) { + this.sendMessage('tx', tx.serialize()); +}; - Connection.prototype.handleData = function (data) { - this.buffers.push(data); +Connection.prototype.sendBlock = function (block, txs) { + var put = new Put(); - if (this.buffers.length > MAX_RECEIVE_BUFFER) { - log.err("Peer "+this.peer+" exceeded maxreceivebuffer, disconnecting."+ - (err.stack ? err.stack : err.toString())); - this.socket.destroy(); - return; - } + // Block header + put.put(block.getHeader()); - this.processData(); - }; + // List of transactions + put.varint(txs.length); + txs.forEach(function (tx) { + put.put(tx.serialize()); + }); - Connection.prototype.processData = function () { - // If there are less than 20 bytes there can't be a message yet. - if (this.buffers.length < 20) return; + this.sendMessage('block', put.buffer()); +}; +Connection.prototype.sendMessage = function (command, payload) { + try { var magic = network.magic; - var i = 0; - for (;;) { - if (this.buffers.get(i ) === magic[0] && - this.buffers.get(i+1) === magic[1] && - this.buffers.get(i+2) === magic[2] && - this.buffers.get(i+3) === magic[3]) { - if (i !== 0) { - log.debug('['+this.peer+'] '+ - 'Received '+i+ - ' bytes of inter-message garbage: '); - log.debug('... '+this.buffers.slice(0,i)); - - this.buffers.skip(i); - } - break; - } + var commandBuf = new Buffer(command, 'ascii'); + if (commandBuf.length > 12) throw 'Command name too long'; - if (i > (this.buffers.length - 4)) { - this.buffers.skip(i); - return; - } - i++; + var checksum; + if (this.sendVer >= 209) { + checksum = doubleSha256(payload).slice(0, 4); + } else { + checksum = new Buffer([]); } - var payloadLen = (this.buffers.get(16) ) + - (this.buffers.get(17) << 8) + - (this.buffers.get(18) << 16) + - (this.buffers.get(19) << 24); + var message = new Put(); // -- HEADER -- + message.put(magic); // magic bytes + message.put(commandBuf); // command name + message.pad(12 - commandBuf.length); // zero-padded + message.word32le(payload.length); // payload length + message.put(checksum); // checksum + // -- BODY -- + message.put(payload); // payload data + + var buffer = message.buffer(); + + log.debug('['+this.peer+'] '+ + "Sending message "+command+" ("+payload.length+" bytes)"); + + this.socket.write(buffer); + } catch (err) { + // TODO: We should catch this error one level higher in order to better + // determine how to react to it. For now though, ignoring it will do. + log.err("Error while sending message to peer "+this.peer+": "+ + (err.stack ? err.stack : err.toString())); + } +}; - var startPos = (this.recvVer >= 209) ? 24 : 20; - var endPos = startPos + payloadLen; +Connection.prototype.handleData = function (data) { + this.buffers.push(data); - if (this.buffers.length < endPos) return; + if (this.buffers.length > MAX_RECEIVE_BUFFER) { + log.err("Peer "+this.peer+" exceeded maxreceivebuffer, disconnecting."+ + (err.stack ? err.stack : err.toString())); + this.socket.destroy(); + return; + } - var command = this.buffers.slice(4, 16).toString('ascii').replace(/\0+$/,""); - var payload = this.buffers.slice(startPos, endPos); - var checksum = (this.recvVer >= 209) ? this.buffers.slice(20, 24) : null; + this.processData(); +}; - log.debug('['+this.peer+'] ' + - "Received message " + command + - " (" + payloadLen + " bytes)"); +Connection.prototype.processData = function () { + // If there are less than 20 bytes there can't be a message yet. + if (this.buffers.length < 20) return; + + var magic = network.magic; + var i = 0; + for (;;) { + if (this.buffers.get(i ) === magic[0] && + this.buffers.get(i+1) === magic[1] && + this.buffers.get(i+2) === magic[2] && + this.buffers.get(i+3) === magic[3]) { + if (i !== 0) { + log.debug('['+this.peer+'] '+ + 'Received '+i+ + ' bytes of inter-message garbage: '); + log.debug('... '+this.buffers.slice(0,i)); - if (checksum !== null) { - var checksumConfirm = doubleSha256(payload).slice(0, 4); - if (buffertools.compare(checksumConfirm, checksum) !== 0) { - log.err('['+this.peer+'] '+ - 'Checksum failed', - { cmd: command, - expected: checksumConfirm.toString('hex'), - actual: checksum.toString('hex') }); - return; + this.buffers.skip(i); } + break; } - var message; - try { - message = this.parseMessage(command, payload); - } catch (e) { - log.err('Error while parsing message '+command+' from ' + - this.peer + ':\n' + - (e.stack ? e.stack : e.toString())); - } - - if (message) { - this.handleMessage(message); + if (i > (this.buffers.length - 4)) { + this.buffers.skip(i); + return; } + i++; + } - this.buffers.skip(endPos); - this.processData(); - }; + var payloadLen = (this.buffers.get(16) ) + + (this.buffers.get(17) << 8) + + (this.buffers.get(18) << 16) + + (this.buffers.get(19) << 24); - Connection.prototype.parseMessage = function (command, payload) { - var parser = new Parser(payload); + var startPos = (this.recvVer >= 209) ? 24 : 20; + var endPos = startPos + payloadLen; - var data = { - command: command - }; + if (this.buffers.length < endPos) return; - var i; + var command = this.buffers.slice(4, 16).toString('ascii').replace(/\0+$/,""); + var payload = this.buffers.slice(startPos, endPos); + var checksum = (this.recvVer >= 209) ? this.buffers.slice(20, 24) : null; - switch (command) { - case 'version': // https://en.bitcoin.it/wiki/Protocol_specification#version - data.version = parser.word32le(); - data.services = parser.word64le(); - data.timestamp = parser.word64le(); - data.addr_me = parser.buffer(26); - data.addr_you = parser.buffer(26); - data.nonce = parser.buffer(8); - data.subversion = parser.varStr(); - data.start_height = parser.word32le(); - break; + log.debug('['+this.peer+'] ' + + "Received message " + command + + " (" + payloadLen + " bytes)"); - case 'inv': - case 'getdata': - data.count = parser.varInt(); + if (checksum !== null) { + var checksumConfirm = doubleSha256(payload).slice(0, 4); + if (buffertools.compare(checksumConfirm, checksum) !== 0) { + log.err('['+this.peer+'] '+ + 'Checksum failed', + { cmd: command, + expected: checksumConfirm.toString('hex'), + actual: checksum.toString('hex') }); + return; + } + } - data.invs = []; - for (i = 0; i < data.count; i++) { - data.invs.push({ - type: parser.word32le(), - hash: parser.buffer(32) - }); - } - break; + var message; + try { + message = this.parseMessage(command, payload); + } catch (e) { + log.err('Error while parsing message '+command+' from ' + + this.peer + ':\n' + + (e.stack ? e.stack : e.toString())); + } - case 'headers': - data.count = parser.varInt(); + if (message) { + this.handleMessage(message); + } - data.headers = []; - for (i = 0; i < data.count; i++) { - var header = new Block(); - header.parse(parser); - data.headers.push(header); - } - break; + this.buffers.skip(endPos); + this.processData(); +}; - case 'block': - var block = new Block(); - block.parse(parser); +Connection.prototype.parseMessage = function (command, payload) { + var parser = new Parser(payload); - data.block = block; - data.version = block.version; - data.prev_hash = block.prev_hash; - data.merkle_root = block.merkle_root; - data.timestamp = block.timestamp; - data.bits = block.bits; - data.nonce = block.nonce; + var data = { + command: command + }; - data.txs = block.txs; + var i; + + switch (command) { + case 'version': // https://en.bitcoin.it/wiki/Protocol_specification#version + data.version = parser.word32le(); + data.services = parser.word64le(); + data.timestamp = parser.word64le(); + data.addr_me = parser.buffer(26); + data.addr_you = parser.buffer(26); + data.nonce = parser.buffer(8); + data.subversion = parser.varStr(); + data.start_height = parser.word32le(); + break; + + case 'inv': + case 'getdata': + data.count = parser.varInt(); + + data.invs = []; + for (i = 0; i < data.count; i++) { + data.invs.push({ + type: parser.word32le(), + hash: parser.buffer(32) + }); + } + break; - data.size = payload.length; - break; + case 'headers': + data.count = parser.varInt(); - case 'tx': - var tx = new Transaction(); - tx.parse(parser); - return { - command: command, - version: tx.version, - lock_time: tx.lock_time, - ins: tx.ins, - outs: tx.outs, - tx: tx, - }; - - case 'getblocks': - case 'getheaders': - // parse out the version - data.version = parser.word32le(); - - // TODO: Limit block locator size? - // reference implementation limits to 500 results - var startCount = parser.varInt(); - - data.starts = []; - for (i = 0; i < startCount; i++) { - data.starts.push(parser.buffer(32)); - } - data.stop = parser.buffer(32); - break; + data.headers = []; + for (i = 0; i < data.count; i++) { + var header = new Block(); +header.parse(parser); +data.headers.push(header); + } + break; + + case 'block': + var block = new Block(); + block.parse(parser); + + data.block = block; + data.version = block.version; + data.prev_hash = block.prev_hash; + data.merkle_root = block.merkle_root; + data.timestamp = block.timestamp; + data.bits = block.bits; + data.nonce = block.nonce; + + data.txs = block.txs; + + data.size = payload.length; + break; + + case 'tx': + var tx = new Transaction(); + tx.parse(parser); + return { + command: command, + version: tx.version, + lock_time: tx.lock_time, + ins: tx.ins, + outs: tx.outs, + tx: tx, + }; - case 'addr': - var addrCount = parser.varInt(); + case 'getblocks': + case 'getheaders': + // parse out the version + data.version = parser.word32le(); - // Enforce a maximum number of addresses per message - if (addrCount > 1000) { - addrCount = 1000; - } + // TODO: Limit block locator size? + // reference implementation limits to 500 results + var startCount = parser.varInt(); - data.addrs = []; - for (i = 0; i < addrCount; i++) { - // TODO: Time actually depends on the version of the other peer (>=31402) - data.addrs.push({ - time: parser.word32le(), - services: parser.word64le(), - ip: parser.buffer(16), - port: parser.word16be() - }); - } - break; + data.starts = []; + for (i = 0; i < startCount; i++) { + data.starts.push(parser.buffer(32)); + } + data.stop = parser.buffer(32); + break; - case 'alert': - data.payload = parser.varStr(); - data.signature = parser.varStr(); - break; + case 'addr': + var addrCount = parser.varInt(); - case 'ping': - if (this.recvVer > BIP0031_VERSION) { - data.nonce = parser.buffer(8); - } - break; + // Enforce a maximum number of addresses per message + if (addrCount > 1000) { + addrCount = 1000; + } - case 'getaddr': - case 'verack': - case 'reject': - // Empty message, nothing to parse - break; + data.addrs = []; + for (i = 0; i < addrCount; i++) { + // TODO: Time actually depends on the version of the other peer (>=31402) + data.addrs.push({ + time: parser.word32le(), + services: parser.word64le(), + ip: parser.buffer(16), + port: parser.word16be() + }); + } + break; - default: - log.err('Connection.parseMessage(): Command not implemented', - {cmd: command}); + case 'alert': + data.payload = parser.varStr(); + data.signature = parser.varStr(); + break; - // This tells the calling function not to issue an event - return null; + case 'ping': + if (this.recvVer > BIP0031_VERSION) { + data.nonce = parser.buffer(8); } + break; - return data; - }; + case 'getaddr': + case 'verack': + case 'reject': + // Empty message, nothing to parse + break; - return Connection; + default: + log.err('Connection.parseMessage(): Command not implemented', + {cmd: command}); + + // This tells the calling function not to issue an event + return null; + } + + return data; }; -module.defineClass(spec); + +module.exports = require('soop')(Connection); diff --git a/Gruntfile.js b/Gruntfile.js index b7ad6c2..dc737c8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,26 +3,28 @@ module.exports = function(grunt) { //Load NPM tasks - grunt.loadNpmTasks('grunt-browserify'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-mocha-test'); grunt.loadNpmTasks('grunt-markdown'); + grunt.loadNpmTasks('grunt-shell'); // Project Configuration grunt.initConfig({ - browserify: { - client: { - src: ['bitcore.js'], - dest: 'browser/bundle.js', + shell: { + browserify: { options: { - debug: true, - alias: [ - 'browserify-bignum/bignumber.js:bignum', - 'browserify-buffertools/buffertools.js:buffertools' - ], - standalone: 'bitcore', - } + stdout: true + }, + command: 'node ./browserify.js > browser/bundle.js', }, + browserifyData: { + options: { + stdout: true + }, + command: 'browserify -t brfs test/testdata.js > browser/testdata.js' + }, + }, + browserify: { test_data: { src: ['test/testdata.js'], dest: 'browser/testdata.js', @@ -40,7 +42,7 @@ module.exports = function(grunt) { }, scripts: { files: ['**/*.js', '**/*.html', '!**/node_modules/**', '!browser/bundle.js', '!browser/testdata.js'], - tasks: ['browserify' /*, 'mochaTest'*/ ], + tasks: ['shell' /*, 'mochaTest'*/ ], }, }, mochaTest: { diff --git a/Opcode.js b/Opcode.js index 5044ab3..5ae4bee 100644 --- a/Opcode.js +++ b/Opcode.js @@ -1,161 +1,158 @@ -require('classtool'); - -function spec(b) { - function Opcode(num) { - this.code = num; - }; - - Opcode.prototype.toString = function () { - return Opcode.reverseMap[this.code]; - }; - - Opcode.map = { - // push value - OP_FALSE : 0, - OP_0 : 0, - OP_PUSHDATA1 : 76, - OP_PUSHDATA2 : 77, - OP_PUSHDATA4 : 78, - OP_1NEGATE : 79, - OP_RESERVED : 80, - OP_TRUE : 81, - OP_1 : 81, - OP_2 : 82, - OP_3 : 83, - OP_4 : 84, - OP_5 : 85, - OP_6 : 86, - OP_7 : 87, - OP_8 : 88, - OP_9 : 89, - OP_10 : 90, - OP_11 : 91, - OP_12 : 92, - OP_13 : 93, - OP_14 : 94, - OP_15 : 95, - OP_16 : 96, - - // control - OP_NOP : 97, - OP_VER : 98, - OP_IF : 99, - OP_NOTIF : 100, - OP_VERIF : 101, - OP_VERNOTIF : 102, - OP_ELSE : 103, - OP_ENDIF : 104, - OP_VERIFY : 105, - OP_RETURN : 106, - - // stack ops - OP_TOALTSTACK : 107, - OP_FROMALTSTACK : 108, - OP_2DROP : 109, - OP_2DUP : 110, - OP_3DUP : 111, - OP_2OVER : 112, - OP_2ROT : 113, - OP_2SWAP : 114, - OP_IFDUP : 115, - OP_DEPTH : 116, - OP_DROP : 117, - OP_DUP : 118, - OP_NIP : 119, - OP_OVER : 120, - OP_PICK : 121, - OP_ROLL : 122, - OP_ROT : 123, - OP_SWAP : 124, - OP_TUCK : 125, - - // splice ops - OP_CAT : 126, - OP_SUBSTR : 127, - OP_LEFT : 128, - OP_RIGHT : 129, - OP_SIZE : 130, - - // bit logic - OP_INVERT : 131, - OP_AND : 132, - OP_OR : 133, - OP_XOR : 134, - OP_EQUAL : 135, - OP_EQUALVERIFY : 136, - OP_RESERVED1 : 137, - OP_RESERVED2 : 138, - - // numeric - OP_1ADD : 139, - OP_1SUB : 140, - OP_2MUL : 141, - OP_2DIV : 142, - OP_NEGATE : 143, - OP_ABS : 144, - OP_NOT : 145, - OP_0NOTEQUAL : 146, - - OP_ADD : 147, - OP_SUB : 148, - OP_MUL : 149, - OP_DIV : 150, - OP_MOD : 151, - OP_LSHIFT : 152, - OP_RSHIFT : 153, - - OP_BOOLAND : 154, - OP_BOOLOR : 155, - OP_NUMEQUAL : 156, - OP_NUMEQUALVERIFY : 157, - OP_NUMNOTEQUAL : 158, - OP_LESSTHAN : 159, - OP_GREATERTHAN : 160, - OP_LESSTHANOREQUAL : 161, - OP_GREATERTHANOREQUAL : 162, - OP_MIN : 163, - OP_MAX : 164, - - OP_WITHIN : 165, - - // crypto - OP_RIPEMD160 : 166, - OP_SHA1 : 167, - OP_SHA256 : 168, - OP_HASH160 : 169, - OP_HASH256 : 170, - OP_CODESEPARATOR : 171, - OP_CHECKSIG : 172, - OP_CHECKSIGVERIFY : 173, - OP_CHECKMULTISIG : 174, - OP_CHECKMULTISIGVERIFY : 175, - - // expansion - OP_NOP1 : 176, - OP_NOP2 : 177, - OP_NOP3 : 178, - OP_NOP4 : 179, - OP_NOP5 : 180, - OP_NOP6 : 181, - OP_NOP7 : 182, - OP_NOP8 : 183, - OP_NOP9 : 184, - OP_NOP10 : 185, - - // template matching params - OP_PUBKEYHASH : 253, - OP_PUBKEY : 254, - OP_INVALIDOPCODE : 255 - }; - - Opcode.reverseMap = []; - - for (var k in Opcode.map) { - if(Opcode.map.hasOwnProperty(k)) { - Opcode.reverseMap[Opcode.map[k]] = k.substr(3); - } - } +var imports = require('soop').imports(); + +function Opcode(num) { + this.code = num; +}; + +Opcode.prototype.toString = function () { + return Opcode.reverseMap[this.code]; +}; - return Opcode; +Opcode.map = { + // push value + OP_FALSE : 0, + OP_0 : 0, + OP_PUSHDATA1 : 76, + OP_PUSHDATA2 : 77, + OP_PUSHDATA4 : 78, + OP_1NEGATE : 79, + OP_RESERVED : 80, + OP_TRUE : 81, + OP_1 : 81, + OP_2 : 82, + OP_3 : 83, + OP_4 : 84, + OP_5 : 85, + OP_6 : 86, + OP_7 : 87, + OP_8 : 88, + OP_9 : 89, + OP_10 : 90, + OP_11 : 91, + OP_12 : 92, + OP_13 : 93, + OP_14 : 94, + OP_15 : 95, + OP_16 : 96, + + // control + OP_NOP : 97, + OP_VER : 98, + OP_IF : 99, + OP_NOTIF : 100, + OP_VERIF : 101, + OP_VERNOTIF : 102, + OP_ELSE : 103, + OP_ENDIF : 104, + OP_VERIFY : 105, + OP_RETURN : 106, + + // stack ops + OP_TOALTSTACK : 107, + OP_FROMALTSTACK : 108, + OP_2DROP : 109, + OP_2DUP : 110, + OP_3DUP : 111, + OP_2OVER : 112, + OP_2ROT : 113, + OP_2SWAP : 114, + OP_IFDUP : 115, + OP_DEPTH : 116, + OP_DROP : 117, + OP_DUP : 118, + OP_NIP : 119, + OP_OVER : 120, + OP_PICK : 121, + OP_ROLL : 122, + OP_ROT : 123, + OP_SWAP : 124, + OP_TUCK : 125, + + // splice ops + OP_CAT : 126, + OP_SUBSTR : 127, + OP_LEFT : 128, + OP_RIGHT : 129, + OP_SIZE : 130, + + // bit logic + OP_INVERT : 131, + OP_AND : 132, + OP_OR : 133, + OP_XOR : 134, + OP_EQUAL : 135, + OP_EQUALVERIFY : 136, + OP_RESERVED1 : 137, + OP_RESERVED2 : 138, + + // numeric + OP_1ADD : 139, + OP_1SUB : 140, + OP_2MUL : 141, + OP_2DIV : 142, + OP_NEGATE : 143, + OP_ABS : 144, + OP_NOT : 145, + OP_0NOTEQUAL : 146, + + OP_ADD : 147, + OP_SUB : 148, + OP_MUL : 149, + OP_DIV : 150, + OP_MOD : 151, + OP_LSHIFT : 152, + OP_RSHIFT : 153, + + OP_BOOLAND : 154, + OP_BOOLOR : 155, + OP_NUMEQUAL : 156, + OP_NUMEQUALVERIFY : 157, + OP_NUMNOTEQUAL : 158, + OP_LESSTHAN : 159, + OP_GREATERTHAN : 160, + OP_LESSTHANOREQUAL : 161, + OP_GREATERTHANOREQUAL : 162, + OP_MIN : 163, + OP_MAX : 164, + + OP_WITHIN : 165, + + // crypto + OP_RIPEMD160 : 166, + OP_SHA1 : 167, + OP_SHA256 : 168, + OP_HASH160 : 169, + OP_HASH256 : 170, + OP_CODESEPARATOR : 171, + OP_CHECKSIG : 172, + OP_CHECKSIGVERIFY : 173, + OP_CHECKMULTISIG : 174, + OP_CHECKMULTISIGVERIFY : 175, + + // expansion + OP_NOP1 : 176, + OP_NOP2 : 177, + OP_NOP3 : 178, + OP_NOP4 : 179, + OP_NOP5 : 180, + OP_NOP6 : 181, + OP_NOP7 : 182, + OP_NOP8 : 183, + OP_NOP9 : 184, + OP_NOP10 : 185, + + // template matching params + OP_PUBKEYHASH : 253, + OP_PUBKEY : 254, + OP_INVALIDOPCODE : 255 }; -module.defineClass(spec); + +Opcode.reverseMap = []; + +for (var k in Opcode.map) { + if(Opcode.map.hasOwnProperty(k)) { + Opcode.reverseMap[Opcode.map[k]] = k.substr(3); + } +} + +module.exports = require('soop')(Opcode); diff --git a/Peer.js b/Peer.js index 54d6635..05be302 100644 --- a/Peer.js +++ b/Peer.js @@ -1,61 +1,58 @@ -require('classtool'); - -function spec(b) { - var Net = b.Net || require('net'); - var Binary = b.Binary || require('binary'); - var buffertools = b.buffertools || require('buffertools'); - - function Peer(host, port, services) { - if ("string" === typeof host) { - if (host.indexOf(':') && !port) { - var parts = host.split(':'); - host = parts[0]; - port = parts[1]; - } - this.host = host; - this.port = +port || 8333; - } else if (host instanceof Peer) { - this.host = host.host; - this.port = host.port; - } else if (Buffer.isBuffer(host)) { - if (buffertools.compare(Peer.IPV6_IPV4_PADDING, host.slice(0, 12)) != 0) { - throw new Error('IPV6 not supported yet! Cannot instantiate host.'); - } - this.host = Array.prototype.slice.apply(host.slice(12)).join('.'); - this.port = +port || 8333; - } else { - throw new Error('Could not instantiate peer, invalid parameter type: ' + - typeof host); +var imports = require('soop').imports(); + +var Net = imports.Net || require('net'); +var Binary = imports.Binary || require('binary'); +var buffertools = imports.buffertools || require('buffertools'); + +function Peer(host, port, services) { + if ("string" === typeof host) { + if (host.indexOf(':') && !port) { + var parts = host.split(':'); + host = parts[0]; + port = parts[1]; } + this.host = host; + this.port = +port || 8333; + } else if (host instanceof Peer) { + this.host = host.host; + this.port = host.port; + } else if (Buffer.isBuffer(host)) { + if (buffertools.compare(Peer.IPV6_IPV4_PADDING, host.slice(0, 12)) != 0) { + throw new Error('IPV6 not supported yet! Cannot instantiate host.'); + } + this.host = Array.prototype.slice.apply(host.slice(12)).join('.'); + this.port = +port || 8333; + } else { + throw new Error('Could not instantiate peer, invalid parameter type: ' + + typeof host); + } + + this.services = (services) ? services : null; + this.lastSeen = 0; +}; - this.services = (services) ? services : null; - this.lastSeen = 0; - }; - - Peer.IPV6_IPV4_PADDING = new Buffer([0,0,0,0,0,0,0,0,0,0,255,255]); - - Peer.prototype.createConnection = function () { - var c = Net.createConnection(this.port, this.host); - return c; - }; +Peer.IPV6_IPV4_PADDING = new Buffer([0,0,0,0,0,0,0,0,0,0,255,255]); - Peer.prototype.getHostAsBuffer = function () { - return new Buffer(this.host.split('.')); - }; +Peer.prototype.createConnection = function () { + var c = Net.createConnection(this.port, this.host); + return c; +}; - Peer.prototype.toString = function () { - return this.host + ":" + this.port; - }; +Peer.prototype.getHostAsBuffer = function () { + return new Buffer(this.host.split('.')); +}; - Peer.prototype.toBuffer = function () { - var put = Binary.put(); - put.word32le(this.lastSeen); - put.word64le(this.services); - put.put(this.getHostAsBuffer()); - put.word16be(this.port); - return put.buffer(); - }; +Peer.prototype.toString = function () { + return this.host + ":" + this.port; +}; - return Peer; +Peer.prototype.toBuffer = function () { + var put = Binary.put(); + put.word32le(this.lastSeen); + put.word64le(this.services); + put.put(this.getHostAsBuffer()); + put.word16be(this.port); + return put.buffer(); }; -module.defineClass(spec); + +module.exports = require('soop')(Peer); diff --git a/PeerManager.js b/PeerManager.js index de38038..e7b6bdc 100644 --- a/PeerManager.js +++ b/PeerManager.js @@ -1,215 +1,214 @@ -require('classtool'); - -function spec(b) { - var config = b.config || require('./config'); - var log = b.log || require('./util/log'); - var network = b.network || require('./networks')[config.network]; - var Connection = b.Connection || require('./Connection').createClass( - {config: config, network: network}); - var Peer = b.Peer || require('./Peer').class(); - var noop = function() {}; - - GetAdjustedTime = b.GetAdjustedTime || function () { - // TODO: Implement actual adjustment - return Math.floor(new Date().getTime() / 1000); - }; - function PeerManager() { - this.active = false; - this.timer = null; +var imports = require('soop').imports(); +var config = imports.config || require('./config'); +var log = imports.log || require('./util/log'); +var network = imports.network || require('./networks')[config.network]; - this.peers = []; - this.connections = []; - this.isConnected = false; - this.peerDiscovery = false; +var Connection = imports.Connection || + require('soop').load('Connection', {config: config, network: network}) || + require ('./Connection'); - // Move these to the Node's settings object - this.interval = 5000; - this.minConnections = 8; - this.minKnownPeers = 10; - }; - PeerManager.superclass = b.superclass || require('events').EventEmitter; +var Peer = imports.Peer || require('./Peer'); - PeerManager.Connection = Connection; +GetAdjustedTime = imports.GetAdjustedTime || function () { + // TODO: Implement actual adjustment + return Math.floor(new Date().getTime() / 1000); +}; - PeerManager.prototype.start = function() { - this.active = true; - if(!this.timer) { - this.timer = setInterval(this.checkStatus.bind(this), this.interval); - } - }; +function PeerManager() { + this.active = false; + this.timer = null; - PeerManager.prototype.stop = function() { - this.active = false; - if(this.timer) { - clearInterval(this.timer); - this.timer = null; - } - for(var i=0; i= 31402 || this.peers.length < 1000)) { - e.conn.sendGetAddr(); - e.conn.getaddr = true; - } - }; + Object.keys(peerIndex).forEach(function(i) { + this.connectTo(peerIndex[i]); + }.bind(this)); + } +}; - PeerManager.prototype.handleReady = function (e) { - log.info('connected to '+e.conn.peer.host+':'+e.conn.peer.port); - this.emit('connect', { - pm: this, - conn: e.conn, - socket: e.socket, - peer: e.peer - }); +PeerManager.prototype.connectTo = function(peer) { + log.info('connecting to '+peer); + try { + return this.addConnection(peer.createConnection(), peer); + } catch (e) { + log.err('creating connection',e); + return null; + } +}; - if(this.isConnected == false) { - this.emit('netConnected'); - this.isConnected = true; - } - }; +PeerManager.prototype.addConnection = function(socketConn, peer) { + var conn = new Connection(socketConn, peer); + this.connections.push(conn); + this.emit('connection', conn); + + conn.addListener('version', this.handleVersion.bind(this)); + conn.addListener('verack', this.handleReady.bind(this)); + conn.addListener('addr', this.handleAddr.bind(this)); + conn.addListener('getaddr', this.handleGetAddr.bind(this)); + conn.addListener('error', this.handleError.bind(this)); + conn.addListener('disconnect', this.handleDisconnect.bind(this)); + + return conn; +}; + +PeerManager.prototype.handleVersion = function(e) { + if (!e.conn.inbound) { + // TODO: Advertise our address (if listening) + } + // Get recent addresses + if(this.peerDiscovery && + (e.message.version >= 31402 || this.peers.length < 1000)) { + e.conn.sendGetAddr(); + e.conn.getaddr = true; + } +}; + +PeerManager.prototype.handleReady = function (e) { + log.info('connected to '+e.conn.peer.host+':'+e.conn.peer.port); + this.emit('connect', { + pm: this, + conn: e.conn, + socket: e.socket, + peer: e.peer + }); + + if(this.isConnected == false) { + this.emit('netConnected'); + this.isConnected = true; + } +}; - PeerManager.prototype.handleAddr = function (e) { - if(!this.peerDiscovery) return; - - var now = GetAdjustedTime(); - e.message.addrs.forEach(function (addr) { - try { - // In case of an invalid time, assume "5 days ago" - if (addr.time <= 100000000 || addr.time > (now + 10 * 60)) { - addr.time = now - 5 * 24 * 60 * 60; - } - var peer = new Peer(addr.ip, addr.port, addr.services); - peer.lastSeen = addr.time; - - // TODO: Handle duplicate peers - this.peers.push(peer); - - // TODO: Handle addr relay - } catch(e) { - log.warn("Invalid addr received: "+e.message); +PeerManager.prototype.handleAddr = function (e) { + if(!this.peerDiscovery) return; + + var now = GetAdjustedTime(); + e.message.addrs.forEach(function (addr) { + try { + // In case of an invalid time, assume "5 days ago" + if (addr.time <= 100000000 || addr.time > (now + 10 * 60)) { + addr.time = now - 5 * 24 * 60 * 60; } - }.bind(this)); - if (e.message.addrs.length < 1000 ) { - e.conn.getaddr = false; + var peer = new Peer(addr.ip, addr.port, addr.services); + peer.lastSeen = addr.time; + + // TODO: Handle duplicate peers + this.peers.push(peer); + + // TODO: Handle addr relay + } catch(e) { + log.warn("Invalid addr received: "+e.message); } - }; + }.bind(this)); + if (e.message.addrs.length < 1000 ) { + e.conn.getaddr = false; + } +}; - PeerManager.prototype.handleGetAddr = function(e) { - // TODO: Reply with addr message. - }; +PeerManager.prototype.handleGetAddr = function(e) { + // TODO: Reply with addr message. +}; - PeerManager.prototype.handleError = function(e) { - log.err('unkown error with peer '+e.peer+' (disconnecting): '+e.err); - this.handleDisconnect.apply(this, [].slice.call(arguments)); - }; +PeerManager.prototype.handleError = function(e) { + log.err('unkown error with peer '+e.peer+' (disconnecting): '+e.err); + this.handleDisconnect.apply(this, [].slice.call(arguments)); +}; - PeerManager.prototype.handleDisconnect = function(e) { - log.info('disconnected from peer '+e.peer); - var i = this.connections.indexOf(e.conn); - if(i != -1) this.connections.splice(i, 1); +PeerManager.prototype.handleDisconnect = function(e) { + log.info('disconnected from peer '+e.peer); + var i = this.connections.indexOf(e.conn); + if(i != -1) this.connections.splice(i, 1); - if(!this.connections.length) { - this.emit('netDisconnected'); - this.isConnected = false; - } - }; + if(!this.connections.length) { + this.emit('netDisconnected'); + this.isConnected = false; + } +}; - PeerManager.prototype.getActiveConnection = function () { - var activeConnections = this.connections.filter(function (conn) { - return conn.active; - }); +PeerManager.prototype.getActiveConnection = function () { + var activeConnections = this.connections.filter(function (conn) { + return conn.active; + }); - if (activeConnections.length) { - var randomIndex = Math.floor(Math.random()*activeConnections.length); - var candidate = activeConnections[randomIndex]; - if (candidate.socket.writable) { - return candidate; - } else { - // Socket is not writable, remove it from active connections - activeConnections.splice(randomIndex, 1); - - // Then try again - // TODO: This causes an infinite recursion when all connections are dead, - // although it shouldn't. - return this.getActiveConnection(); - } + if (activeConnections.length) { + var randomIndex = Math.floor(Math.random()*activeConnections.length); + var candidate = activeConnections[randomIndex]; + if (candidate.socket.writable) { + return candidate; } else { - return null; - } - }; + // Socket is not writable, remove it from active connections + activeConnections.splice(randomIndex, 1); - PeerManager.prototype.getActiveConnections = function () { - return this.connections.slice(0); - }; + // Then try again + // TODO: This causes an infinite recursion when all connections are dead, + // although it shouldn't. + return this.getActiveConnection(); + } + } else { + return null; + } +}; - return PeerManager; +PeerManager.prototype.getActiveConnections = function () { + return this.connections.slice(0); }; -module.defineClass(spec); + +module.exports = require('soop')(PeerManager); diff --git a/PrivateKey.js b/PrivateKey.js index decc9c7..c9a811c 100644 --- a/PrivateKey.js +++ b/PrivateKey.js @@ -1,67 +1,64 @@ -require('classtool'); +var imports = require('soop').imports(); -function ClassSpec(b) { - var superclass = b.superclass || require('./util/VersionedData').class(); +var parent = imports.parent || require('./util/VersionedData'); - //compressed is true if public key is compressed; false otherwise - function PrivateKey(version, buf, compressed) { - PrivateKey.super(this, arguments); - if (compressed !== undefined) - this.compressed(compressed); - }; +//compressed is true if public key is compressed; false otherwise +function PrivateKey(version, buf, compressed) { + PrivateKey.super(this, arguments); + if (compressed !== undefined) + this.compressed(compressed); +}; - PrivateKey.superclass = superclass; - superclass.applyEncodingsTo(PrivateKey); +PrivateKey.parent = parent; +parent.applyEncodingsTo(PrivateKey); - PrivateKey.prototype.validate = function() { - this.doAsBinary(function() { - PrivateKey.super(this, 'validate', arguments); - if (this.data.length < 32 || (this.data.length > 1+32 && !this.compressed()) || (this.data.length==1+32+1 && this.data[1+32+1-1]!=1) || this.data.length>1+32+1) - throw new Error('invalid data length'); - }); - }; +PrivateKey.prototype.validate = function() { + this.doAsBinary(function() { + PrivateKey.super(this, 'validate', arguments); + if (this.data.length < 32 || (this.data.length > 1+32 && !this.compressed()) || (this.data.length==1+32+1 && this.data[1+32+1-1]!=1) || this.data.length>1+32+1) + throw new Error('invalid data length'); + }); +}; - // get or set the payload data (as a Buffer object) - // overloaded from VersionedData - PrivateKey.prototype.payload = function(data) { - if(data) { - this.doAsBinary(function() {data.copy(this.data,1);}); - return data; - } - var buf=this.as('binary'); - if (buf.length==1+32+1) - return buf.slice(1,1+32); - else if (buf.length==1+32) - return buf.slice(1); - }; +// get or set the payload data (as a Buffer object) +// overloaded from VersionedData +PrivateKey.prototype.payload = function(data) { + if(data) { + this.doAsBinary(function() {data.copy(this.data,1);}); + return data; + } + var buf=this.as('binary'); + if (buf.length==1+32+1) + return buf.slice(1,1+32); + else if (buf.length==1+32) + return buf.slice(1); +}; - // get or set whether the corresponding public key is compressed - PrivateKey.prototype.compressed = function(compressed) { - if (compressed !== undefined) { - this.doAsBinary(function(){ - var len=1+32+1; - if (compressed) { - var data=new Buffer(len); - this.data.copy(data); - this.data=data; - this.data[len-1]=1; - } else { - this.data=this.data.slice(0,len-1); - } - }); - } - else { +// get or set whether the corresponding public key is compressed +PrivateKey.prototype.compressed = function(compressed) { + if (compressed !== undefined) { + this.doAsBinary(function(){ var len=1+32+1; - var data=this.as('binary'); - if (data.length==len && data[len-1]==1) - return true; - else if (data.length==len-1) - return false; - else - throw new Error('invalid private key'); - } - }; - - return PrivateKey; + if (compressed) { + var data=new Buffer(len); + this.data.copy(data); + this.data=data; + this.data[len-1]=1; + } else { + this.data=this.data.slice(0,len-1); + } + }); + } + else { + var len=1+32+1; + var data=this.as('binary'); + if (data.length==len && data[len-1]==1) + return true; + else if (data.length==len-1) + return false; + else + throw new Error('invalid private key'); + } }; -module.defineClass(ClassSpec); + +module.exports = require('soop')(PrivateKey); diff --git a/README.md b/README.md index 351da25..09cd6f2 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ Bitcore runs on [node](http://nodejs.org/), and can be installed via [npm](https npm install bitcore ``` -It is a collection of objects useful to bitcoin applications; class-like idioms are enabled via [Classtool](https://github.com/gasteve/classtool). In most cases, a developer will require the object's class directly: +It is a collection of objects useful to bitcoin applications; class-like idioms are enabled via [Soop](https://github.com/gasteve/soop). In most cases, a developer will require the object's class directly: ``` -var Address = require('bitcore/Address').class(); +var Address = require('bitcore/Address'); ``` #Examples @@ -29,7 +29,7 @@ Some examples are provided at the [examples](/examples) path. Here are some snip ## Validating an address Validating a Bitcoin address: ```js -var Address = require('bitcore/Address').class(); +var Address = require('bitcore/Address'); var addrStrings = [ "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", @@ -57,10 +57,9 @@ For this example you need a running bitcoind instance with RPC enabled. ```js var util = require('util'); var networks = require('bitcore/networks'); -var Peer = require('bitcore/Peer').class(); -var PeerManager = require('bitcore/PeerManager').createClass({ - network: networks.testnet -}); +var Peer = require('bitcore/Peer'); +var PeerManager = require('soop').load('bitcore/PeerManager', + {network: networks.testnet}); var handleBlock = function(info) { @@ -109,14 +108,13 @@ PeerManager will emit the following events: 'version', 'verack', 'addr', 'getadd For this example you need a running bitcoind instance with RPC enabled. ```js var networks = require('bitcore/networks'); -var Peer = require('bitcore/Peer').class(); -var Transaction = require('bitcore/Transaction').class(); -var Address = require('bitcore/Address').class(); -var Script = require('bitcore/Script').class(); +var Peer = require('bitcore/Peer'); +var Transaction = require('bitcore/Transaction'); +var Address = require('bitcore/Address'); +var Script = require('bitcore/Script'); var coinUtil = require('bitcore/util/util'); -var PeerManager = require('bitcore/PeerManager').createClass({ - network: networks.testnet -}); +var PeerManager = require('soop').load('bitcore/PeerManager', + {network: networks.testnet}); var createTx = function() { @@ -185,7 +183,7 @@ peerman.start(); For this example you need a running bitcoind instance with RPC enabled. ```js var util = require('util'); -var RpcClient = require('bitcore/RpcClient').class(); +var RpcClient = require('bitcore/RpcClient'); var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4'; var config = { @@ -217,7 +215,7 @@ Check the list of all supported RPC call at [RpcClient.js](RpcClient.js) Gets an address strings from a ScriptPubKey Buffer ``` - var Address = require('bitcore/Address').class(); + var Address = require('bitcore/Address'); var coinUtil= require('bitcore/util/util'); var getAddrStr = function(s) { diff --git a/RpcClient.js b/RpcClient.js index 38df00f..54dff58 100644 --- a/RpcClient.js +++ b/RpcClient.js @@ -1,210 +1,208 @@ // RpcClient.js // MIT/X11-like license. See LICENSE.txt. // Copyright 2013 BitPay, Inc. -require('classtool'); +// +var imports = require('soop').imports(); +var http = imports.http || require('http'); +var https = imports.https || require('https'); +var log = imports.log || require('./util/log'); -function ClassSpec(b) { - var http = b.http || require('http'); - var https = b.https || require('https'); - var log = b.log || require('./util/log'); +function RpcClient(opts) { + opts = opts || {}; + this.host = opts.host || '127.0.0.1'; + this.port = opts.port || 8332; + this.user = opts.user || 'user'; + this.pass = opts.pass || 'pass'; + this.protocol = (opts.protocol == 'http') ? http : https; + this.batchedCalls = null; + this.disableAgent = opts.disableAgent || false; +} + +RpcClient.prototype.batch = function(batchCallback, resultCallback) { + this.batchedCalls = []; + batchCallback(); + rpc.call(this, this.batchedCalls, resultCallback); + this.batchedCalls = null; +} - function RpcClient(opts) { - opts = opts || {}; - this.host = opts.host || '127.0.0.1'; - this.port = opts.port || 8332; - this.user = opts.user || 'user'; - this.pass = opts.pass || 'pass'; - this.protocol = (opts.protocol == 'http') ? http : https; - this.batchedCalls = null; - this.disableAgent = opts.disableAgent || false; - } - - RpcClient.prototype.batch = function(batchCallback, resultCallback) { - this.batchedCalls = []; - batchCallback(); - rpc.call(this, this.batchedCalls, resultCallback); - this.batchedCalls = null; - } - - var callspec = { - addMultiSigAddress: '', - addNode: '', - backupWallet: '', - createMultiSig: '', - createRawTransaction: '', - decodeRawTransaction: '', - dumpPrivKey: '', - encryptWallet: '', - getAccount: '', - getAccountAddress: 'str', - getAddedNodeInfo: '', - getAddressesByAccount: '', - getBalance: 'str int', - getBestBlockHash: '', - getBlock: '', - getBlockCount: '', - getBlockHash: 'int', - getBlockNumber: '', - getBlockTemplate: '', - getConnectionCount: '', - getDifficulty: '', - getGenerate: '', - getHashesPerSec: '', - getInfo: '', - getMemoryPool: '', - getMiningInfo: '', - getNewAddress: '', - getPeerInfo: '', - getRawMemPool: '', - getRawTransaction: 'str int', - getReceivedByAccount: 'str int', - getReceivedByAddress: 'str int', - getTransaction: '', - getTxOut: 'str int bool', - getTxOutSetInfo: '', - getWork: '', - help: '', - importAddress: 'str str bool', - importPrivKey: 'str str bool', - keyPoolRefill: '', - listAccounts: 'int', - listAddressGroupings: '', - listReceivedByAccount: 'int bool', - listReceivedByAddress: 'int bool', - listSinceBlock: 'str int', - listTransactions: 'str int int', - listUnspent: 'int int', - listLockUnspent: 'bool', - lockUnspent: '', - move: 'str str float int str', - sendFrom: 'str str float int str str', - sendMany: 'str str int str', //not sure this is will work - sendRawTransaction: '', - sendToAddress: 'str float str str', - setAccount: '', - setGenerate: 'bool int', - setTxFee: 'float', - signMessage: '', - signRawTransaction: '', - stop: '', - submitBlock: '', - validateAddress: '', - verifyMessage: '', - walletLock: '', - walletPassPhrase: 'string int', - walletPassphraseChange: '', - }; +var callspec = { + addMultiSigAddress: '', + addNode: '', + backupWallet: '', + createMultiSig: '', + createRawTransaction: '', + decodeRawTransaction: '', + dumpPrivKey: '', + encryptWallet: '', + getAccount: '', + getAccountAddress: 'str', + getAddedNodeInfo: '', + getAddressesByAccount: '', + getBalance: 'str int', + getBestBlockHash: '', + getBlock: '', + getBlockCount: '', + getBlockHash: 'int', + getBlockNumber: '', + getBlockTemplate: '', + getConnectionCount: '', + getDifficulty: '', + getGenerate: '', + getHashesPerSec: '', + getInfo: '', + getMemoryPool: '', + getMiningInfo: '', + getNewAddress: '', + getPeerInfo: '', + getRawMemPool: '', + getRawTransaction: 'str int', + getReceivedByAccount: 'str int', + getReceivedByAddress: 'str int', + getTransaction: '', + getTxOut: 'str int bool', + getTxOutSetInfo: '', + getWork: '', + help: '', + importAddress: 'str str bool', + importPrivKey: 'str str bool', + keyPoolRefill: '', + listAccounts: 'int', + listAddressGroupings: '', + listReceivedByAccount: 'int bool', + listReceivedByAddress: 'int bool', + listSinceBlock: 'str int', + listTransactions: 'str int int', + listUnspent: 'int int', + listLockUnspent: 'bool', + lockUnspent: '', + move: 'str str float int str', + sendFrom: 'str str float int str str', + sendMany: 'str str int str', //not sure this is will work + sendRawTransaction: '', + sendToAddress: 'str float str str', + setAccount: '', + setGenerate: 'bool int', + setTxFee: 'float', + signMessage: '', + signRawTransaction: '', + stop: '', + submitBlock: '', + validateAddress: '', + verifyMessage: '', + walletLock: '', + walletPassPhrase: 'string int', + walletPassphraseChange: '', +}; - var slice = function(arr, start, end) { - return Array.prototype.slice.call(arr, start, end); - }; +var slice = function(arr, start, end) { + return Array.prototype.slice.call(arr, start, end); +}; - function generateRPCMethods(constructor, apiCalls, rpc) { - function createRPCMethod(methodName, argMap) { - return function() { - var limit = arguments.length - 1; - if(this.batchedCalls) var limit = arguments.length; - for (var i=0; i 0 && opcode < OP_PUSHDATA1) { + // Read some bytes of data, opcode value is the length of data + this.chunks.push(parser.buffer(opcode)); + } else if (opcode == OP_PUSHDATA1) { + len = parser.word8(); + this.chunks.push(parser.buffer(len)); + } else if (opcode == OP_PUSHDATA2) { + len = parser.word16le(); + this.chunks.push(parser.buffer(len)); + } else if (opcode == OP_PUSHDATA4) { + len = parser.word32le(); + this.chunks.push(parser.buffer(len)); + } else { + this.chunks.push(opcode); + } + } +}; -function spec(b) { - var config = b.config || require('./config'); - var log = b.log || require('./util/log'); +Script.prototype.isPushOnly = function() { + for (var i = 0; i < this.chunks.length; i++) + if (!Buffer.isBuffer(this.chunks[i])) + return false; - var Opcode = b.Opcode || require('./Opcode').class(); - var buffertools = b.buffertools || require('buffertools'); + return true; +}; - // Make opcodes available as pseudo-constants - for (var i in Opcode.map) { - eval(i + " = " + Opcode.map[i] + ";"); - } +Script.prototype.isP2SH = function() { + return (this.chunks.length == 3 && + this.chunks[0] == OP_HASH160 && + Buffer.isBuffer(this.chunks[1]) && + this.chunks[1].length == 20 && + this.chunks[2] == OP_EQUAL); +}; - var util = b.util || require('./util/util'); - var Parser = b.Parser || require('./util/BinaryParser').class(); - var Put = b.Put || require('bufferput'); - - var TX_UNKNOWN = 0; - var TX_PUBKEY = 1; - var TX_PUBKEYHASH = 2; - var TX_MULTISIG = 3; - var TX_SCRIPTHASH = 4; - - var TX_TYPES = [ - 'unknown', - 'pubkey', - 'pubkeyhash', - 'multisig', - 'scripthash' - ]; - - function Script(buffer) { - if (buffer) { - this.buffer = buffer; - } else { - this.buffer = util.EMPTY_BUFFER; - } - this.chunks = []; - this.parse(); - }; - this.class = Script; - - Script.TX_UNKNOWN = TX_UNKNOWN; - Script.TX_PUBKEY = TX_PUBKEY; - Script.TX_PUBKEYHASH = TX_PUBKEYHASH; - Script.TX_MULTISIG = TX_MULTISIG; - Script.TX_SCRIPTHASH = TX_SCRIPTHASH; - - Script.prototype.parse = function() { - this.chunks = []; - - var parser = new Parser(this.buffer); - while (!parser.eof()) { - var opcode = parser.word8(); - - var len; - if (opcode > 0 && opcode < OP_PUSHDATA1) { - // Read some bytes of data, opcode value is the length of data - this.chunks.push(parser.buffer(opcode)); - } else if (opcode == OP_PUSHDATA1) { - len = parser.word8(); - this.chunks.push(parser.buffer(len)); - } else if (opcode == OP_PUSHDATA2) { - len = parser.word16le(); - this.chunks.push(parser.buffer(len)); - } else if (opcode == OP_PUSHDATA4) { - len = parser.word32le(); - this.chunks.push(parser.buffer(len)); - } else { - this.chunks.push(opcode); - } - } - }; +Script.prototype.isPubkey = function() { + return (this.chunks.length == 2 && + Buffer.isBuffer(this.chunks[0]) && + this.chunks[1] == OP_CHECKSIG); +}; - Script.prototype.isPushOnly = function() { - for (var i = 0; i < this.chunks.length; i++) - if (!Buffer.isBuffer(this.chunks[i])) - return false; +Script.prototype.isPubkeyHash = function() { + return (this.chunks.length == 5 && + this.chunks[0] == OP_DUP && + this.chunks[1] == OP_HASH160 && + Buffer.isBuffer(this.chunks[2]) && + this.chunks[2].length == 20 && + this.chunks[3] == OP_EQUALVERIFY && + this.chunks[4] == OP_CHECKSIG); +}; + +function isSmallIntOp(opcode) { + return ((opcode == OP_0) || + ((opcode >= OP_1) && (opcode <= OP_16))); +}; + +Script.prototype.isMultiSig = function() { + return (this.chunks.length > 3 && + isSmallIntOp(this.chunks[0]) && + isSmallIntOp(this.chunks[this.chunks.length - 2]) && + this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG); +}; + +Script.prototype.finishedMultiSig = function() { + var nsigs = 0; + for (var i = 0; i < this.chunks.length - 1; i++) + if (this.chunks[i] !== 0) + nsigs++; + var serializedScript = this.chunks[this.chunks.length - 1]; + var script = new Script(serializedScript); + var nreq = script.chunks[0] - 80; //see OP_2-OP_16 + + if (nsigs == nreq) return true; - }; - - Script.prototype.isP2SH = function() { - return (this.chunks.length == 3 && - this.chunks[0] == OP_HASH160 && - Buffer.isBuffer(this.chunks[1]) && - this.chunks[1].length == 20 && - this.chunks[2] == OP_EQUAL); - }; - - Script.prototype.isPubkey = function() { - return (this.chunks.length == 2 && - Buffer.isBuffer(this.chunks[0]) && - this.chunks[1] == OP_CHECKSIG); - }; - - Script.prototype.isPubkeyHash = function() { - return (this.chunks.length == 5 && - this.chunks[0] == OP_DUP && - this.chunks[1] == OP_HASH160 && - Buffer.isBuffer(this.chunks[2]) && - this.chunks[2].length == 20 && - this.chunks[3] == OP_EQUALVERIFY && - this.chunks[4] == OP_CHECKSIG); - }; - - function isSmallIntOp(opcode) { - return ((opcode == OP_0) || - ((opcode >= OP_1) && (opcode <= OP_16))); - }; - - Script.prototype.isMultiSig = function() { - return (this.chunks.length > 3 && - isSmallIntOp(this.chunks[0]) && - isSmallIntOp(this.chunks[this.chunks.length - 2]) && - this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG); - }; - - Script.prototype.finishedMultiSig = function() { - var nsigs = 0; - for (var i = 0; i < this.chunks.length - 1; i++) - if (this.chunks[i] !== 0) - nsigs++; - - var serializedScript = this.chunks[this.chunks.length - 1]; - var script = new Script(serializedScript); - var nreq = script.chunks[0] - 80; //see OP_2-OP_16 - - if (nsigs == nreq) - return true; - else - return false; + else + return false; +} + +Script.prototype.removePlaceHolders = function() { + var chunks = []; + for (var i in this.chunks) { + if (this.chunks.hasOwnProperty(i)) { + var chunk = this.chunks[i]; + if (chunk != 0) + chunks.push(chunk); + } } - - Script.prototype.removePlaceHolders = function() { - var chunks = []; - for (var i in this.chunks) { - if (this.chunks.hasOwnProperty(i)) { - var chunk = this.chunks[i]; - if (chunk != 0) - chunks.push(chunk); - } + this.chunks = chunks; + this.updateBuffer(); + return this; +} + +Script.prototype.prependOp0 = function() { + var chunks = [0]; + for (i in this.chunks) { + if (this.chunks.hasOwnProperty(i)) { + chunks.push(this.chunks[i]); } - this.chunks = chunks; - this.updateBuffer(); - return this; } + this.chunks = chunks; + this.updateBuffer(); + return this; +} + +// is this a script form we know? +Script.prototype.classify = function() { + if (this.isPubkeyHash()) + return TX_PUBKEYHASH; + if (this.isP2SH()) + return TX_SCRIPTHASH; + if (this.isMultiSig()) + return TX_MULTISIG; + if (this.isPubkey()) + return TX_PUBKEY; + return TX_UNKNOWN; +}; - Script.prototype.prependOp0 = function() { - var chunks = [0]; - for (i in this.chunks) { - if (this.chunks.hasOwnProperty(i)) { - chunks.push(this.chunks[i]); - } - } - this.chunks = chunks; - this.updateBuffer(); - return this; +// extract useful data items from known scripts +Script.prototype.capture = function() { + var txType = this.classify(); + var res = []; + switch (txType) { + case TX_PUBKEY: + res.push(this.chunks[0]); + break; + case TX_PUBKEYHASH: + res.push(this.chunks[2]); + break; + case TX_MULTISIG: + for (var i = 1; i < (this.chunks.length - 2); i++) + res.push(this.chunks[i]); + break; + case TX_SCRIPTHASH: + res.push(this.chunks[1]); + break; + + case TX_UNKNOWN: + default: + // do nothing + break; } - // is this a script form we know? - Script.prototype.classify = function() { - if (this.isPubkeyHash()) - return TX_PUBKEYHASH; - if (this.isP2SH()) - return TX_SCRIPTHASH; - if (this.isMultiSig()) - return TX_MULTISIG; - if (this.isPubkey()) - return TX_PUBKEY; - return TX_UNKNOWN; - }; - - // extract useful data items from known scripts - Script.prototype.capture = function() { - var txType = this.classify(); - var res = []; - switch (txType) { - case TX_PUBKEY: - res.push(this.chunks[0]); - break; - case TX_PUBKEYHASH: - res.push(this.chunks[2]); - break; - case TX_MULTISIG: - for (var i = 1; i < (this.chunks.length - 2); i++) - res.push(this.chunks[i]); - break; - case TX_SCRIPTHASH: - res.push(this.chunks[1]); - break; - - case TX_UNKNOWN: - default: - // do nothing - break; - } + return res; +}; - return res; - }; - - // return first extracted data item from script - Script.prototype.captureOne = function() { - var arr = this.capture(); - return arr[0]; - }; - - Script.prototype.getOutType = function() { - var txType = this.classify(); - switch (txType) { - case TX_PUBKEY: - return 'Pubkey'; - case TX_PUBKEYHASH: - return 'Address'; - default: - return 'Strange'; - } - }; - - Script.prototype.getRawOutType = function() { - return TX_TYPES[this.classify()]; - }; - - Script.prototype.simpleOutHash = function() { - switch (this.getOutType()) { - case 'Address': - return this.chunks[2]; - case 'Pubkey': - return util.sha256ripe160(this.chunks[0]); - default: - log.debug("Encountered non-standard scriptPubKey"); - log.debug("Strange script was: " + this.toString()); - return null; - } - }; +// return first extracted data item from script +Script.prototype.captureOne = function() { + var arr = this.capture(); + return arr[0]; +}; - Script.prototype.getInType = function() { - if (this.chunks.length == 1) { - // Direct IP to IP transactions only have the public key in their scriptSig. +Script.prototype.getOutType = function() { + var txType = this.classify(); + switch (txType) { + case TX_PUBKEY: return 'Pubkey'; - } else if (this.chunks.length == 2 && - Buffer.isBuffer(this.chunks[0]) && - Buffer.isBuffer(this.chunks[1])) { + case TX_PUBKEYHASH: return 'Address'; - } else { + default: return 'Strange'; - } - }; - - Script.prototype.simpleInPubKey = function() { - switch (this.getInType()) { - case 'Address': - return this.chunks[1]; - case 'Pubkey': - return null; - default: - log.debug("Encountered non-standard scriptSig"); - log.debug("Strange script was: " + this.toString()); - return null; - } - }; - - Script.prototype.getBuffer = function() { - return this.buffer; - }; - - Script.fromStringContent = function(s) { - var chunks = []; - var split = s.split(' '); - for (var i = 0; i < split.length; i++) { - var word = split[i]; - if (word.length > 2 && word.substring(0, 2) === '0x') { - chunks.push(new Buffer(word.substring(2, word.length), 'hex')); + } +}; + +Script.prototype.getRawOutType = function() { + return TX_TYPES[this.classify()]; +}; + +Script.prototype.simpleOutHash = function() { + switch (this.getOutType()) { + case 'Address': + return this.chunks[2]; + case 'Pubkey': + return util.sha256ripe160(this.chunks[0]); + default: + log.debug("Encountered non-standard scriptPubKey"); + log.debug("Strange script was: " + this.toString()); + return null; + } +}; + +Script.prototype.getInType = function() { + if (this.chunks.length == 1) { + // Direct IP to IP transactions only have the public key in their scriptSig. + return 'Pubkey'; + } else if (this.chunks.length == 2 && + Buffer.isBuffer(this.chunks[0]) && + Buffer.isBuffer(this.chunks[1])) { + return 'Address'; + } else { + return 'Strange'; + } +}; + +Script.prototype.simpleInPubKey = function() { + switch (this.getInType()) { + case 'Address': + return this.chunks[1]; + case 'Pubkey': + return null; + default: + log.debug("Encountered non-standard scriptSig"); + log.debug("Strange script was: " + this.toString()); + return null; + } +}; + +Script.prototype.getBuffer = function() { + return this.buffer; +}; + +Script.fromStringContent = function(s) { + var chunks = []; + var split = s.split(' '); + for (var i = 0; i < split.length; i++) { + var word = split[i]; + if (word.length > 2 && word.substring(0, 2) === '0x') { + chunks.push(new Buffer(word.substring(2, word.length), 'hex')); + } else { + var opcode = Opcode.map['OP_' + word]; + if (opcode) { + chunks.push(opcode); } else { - var opcode = Opcode.map['OP_' + word]; - if (opcode) { - chunks.push(opcode); - } else { - var integer = parseInt(word); - if (!isNaN(integer)) { - //console.log(integer+' bits=\t'+integer.toString(2).replace('-','').length); - var data = util.intToBuffer(integer); - chunks.push(data); - } + var integer = parseInt(word); + if (!isNaN(integer)) { + //console.log(integer+' bits=\t'+integer.toString(2).replace('-','').length); + var data = util.intToBuffer(integer); + chunks.push(data); } } } - return Script.fromChunks(chunks); - }; + } + return Script.fromChunks(chunks); +}; + +Script.prototype.getStringContent = function(truncate, maxEl) { + if (truncate === null) { + truncate = true; + } + + if ('undefined' === typeof maxEl) { + maxEl = 15; + } - Script.prototype.getStringContent = function(truncate, maxEl) { - if (truncate === null) { - truncate = true; + var s = ''; + for (var i = 0, l = this.chunks.length; i < l; i++) { + var chunk = this.chunks[i]; + + if (i > 0) { + s += ' '; } - if ('undefined' === typeof maxEl) { - maxEl = 15; + if (Buffer.isBuffer(chunk)) { + s += '0x' + util.formatBuffer(chunk, truncate ? null : 0); + } else { + s += Opcode.reverseMap[chunk]; } - var s = ''; - for (var i = 0, l = this.chunks.length; i < l; i++) { - var chunk = this.chunks[i]; + if (maxEl && i > maxEl) { + s += ' ...'; + break; + } + } + return s; +}; - if (i > 0) { - s += ' '; - } +Script.prototype.toString = function(truncate, maxEl) { + var script = " diff --git a/package.json b/package.json index 68fb6e2..9d30c43 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "test": "mocha test -R spec" }, "dependencies": { - "classtool": "git://github.com/bitpay/classtool.git", + "soop": "git://github.com/matiu/node-soop.git#feature/browser-support", "base58-native": "=0.1.3", "bindings": "=1.1.1", "bufferput": "git://github.com/bitpay/node-bufferput.git", @@ -57,7 +57,8 @@ "devDependencies": { "grunt-contrib-watch": "~0.5.3", "grunt-mocha-test": "~0.8.2", - "grunt-browserify": "~1.3.0", + "grunt-shell": "~0.6.4", + "browser-pack": "*", "grunt-markdown": "~0.5.0", "mocha": ">=1.15.1", "browserify-bignum": "git://github.com/maraoz/browserify-bignum.git", diff --git a/test/index.html b/test/index.html index bbd0d61..9946f5c 100644 --- a/test/index.html +++ b/test/index.html @@ -13,7 +13,14 @@ - + + + + + diff --git a/test/test.Address.js b/test/test.Address.js index 5713cf5..758440e 100644 --- a/test/test.Address.js +++ b/test/test.Address.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('Address', function() { should.exist(AddressModule); }); it('should be able to create class', function() { - Address = AddressModule.class(); + Address = AddressModule; should.exist(Address); }); it('should be able to create instance', function() { diff --git a/test/test.Block.js b/test/test.Block.js index a9deb08..f159f43 100644 --- a/test/test.Block.js +++ b/test/test.Block.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); var BlockModule = bitcore.Block; @@ -12,7 +12,7 @@ describe('Block', function() { should.exist(BlockModule); }); it('should be able to create class', function() { - Block = BlockModule.class(); + Block = BlockModule; should.exist(Block); }); it('should be able to create instance', function() { diff --git a/test/test.Bloom.js b/test/test.Bloom.js index bec1f51..80504ac 100644 --- a/test/test.Bloom.js +++ b/test/test.Bloom.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('Bloom', function() { should.exist(BloomModule); }); it('should be able to create class', function() { - Bloom = BloomModule.class(); + Bloom = BloomModule; should.exist(Bloom); }); it('should be able to create instance', function() { diff --git a/test/test.Connection.js b/test/test.Connection.js index d1be479..8682973 100644 --- a/test/test.Connection.js +++ b/test/test.Connection.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -14,7 +14,7 @@ describe('Connection', function() { should.exist(ConnectionModule); }); it('should be able to create class', function() { - Connection = ConnectionModule.class(); + Connection = ConnectionModule; should.exist(Connection); }); it('should be able to create instance', function() { diff --git a/test/test.EncodedData.js b/test/test.EncodedData.js index 26c657b..e269661 100644 --- a/test/test.EncodedData.js +++ b/test/test.EncodedData.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('EncodedData', function() { should.exist(EncodedDataModule); }); it('should be able to create class', function() { - EncodedData = EncodedDataModule.class(); + EncodedData = EncodedDataModule; should.exist(EncodedData); }); it('should be able to create an instance', function() { diff --git a/test/test.Key.js b/test/test.Key.js index 975964d..3029008 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var buffertools = require('buffertools'); diff --git a/test/test.Opcode.js b/test/test.Opcode.js index fa78f5e..4c9a5e9 100644 --- a/test/test.Opcode.js +++ b/test/test.Opcode.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('Opcode', function() { should.exist(OpcodeModule); }); it('should be able to create class', function() { - Opcode = OpcodeModule.class(); + Opcode = OpcodeModule; should.exist(Opcode); }); it('should be able to create instance', function() { diff --git a/test/test.Peer.js b/test/test.Peer.js index 477574b..0a0f0fd 100644 --- a/test/test.Peer.js +++ b/test/test.Peer.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('Peer', function() { should.exist(PeerModule); }); it('should be able to create class', function() { - Peer = PeerModule.class(); + Peer = PeerModule; should.exist(Peer); }); it('should be able to create instance', function() { diff --git a/test/test.PeerManager.js b/test/test.PeerManager.js index fc36379..b60de02 100644 --- a/test/test.PeerManager.js +++ b/test/test.PeerManager.js @@ -1,11 +1,12 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); -var PeerManagerModule = bitcore.PeerManager; +var PeerManagerModule = bitcore.PeerManager || require('PeerManager'); + var PeerManager; describe('PeerManager', function() { @@ -13,7 +14,7 @@ describe('PeerManager', function() { should.exist(PeerManagerModule); }); it('should be able to create class', function() { - PeerManager = PeerManagerModule.class(); + PeerManager = PeerManagerModule; should.exist(PeerManager); }); it('should be able to create instance', function() { diff --git a/test/test.PrivateKey.js b/test/test.PrivateKey.js index c096426..f6508e5 100644 --- a/test/test.PrivateKey.js +++ b/test/test.PrivateKey.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -15,7 +15,7 @@ describe('PrivateKey', function() { should.exist(PrivateKeyModule); }); it('should be able to create class', function() { - PrivateKey = PrivateKeyModule.class(); + PrivateKey = PrivateKeyModule; should.exist(PrivateKey); }); it('should be able to create instance', function() { diff --git a/test/test.RpcClient.js b/test/test.RpcClient.js index cbc795a..13ed435 100644 --- a/test/test.RpcClient.js +++ b/test/test.RpcClient.js @@ -1,13 +1,13 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); var RpcClientModule = bitcore.RpcClient; var RpcClient; - RpcClient = RpcClientModule.class(); + RpcClient = RpcClientModule; describe('RpcClient', function() { it('should initialze the main object', function() { diff --git a/test/test.SIN.js b/test/test.SIN.js index 12c32fc..b71dadf 100644 --- a/test/test.SIN.js +++ b/test/test.SIN.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('SIN', function() { should.exist(SINModule); }); it('should be able to create class', function() { - SIN = SINModule.class(); + SIN = SINModule; should.exist(SIN); }); it('should be able to create instance', function() { diff --git a/test/test.SINKey.js b/test/test.SINKey.js index 469c318..3b66308 100644 --- a/test/test.SINKey.js +++ b/test/test.SINKey.js @@ -1,10 +1,7 @@ 'use strict'; - - -var chai = require('chai'); -var bitcore = require('../bitcore'); - +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); var SINKeyModule = bitcore.SINKey; @@ -16,7 +13,7 @@ describe('SINKey', function() { should.exist(SINKeyModule); }); it('should be able to create class', function() { - SINKey = SINKeyModule.class(); + SINKey = SINKeyModule; should.exist(SINKey); }); it('should be able to create instance', function() { diff --git a/test/test.Script.js b/test/test.Script.js index 52294fd..31d1a80 100644 --- a/test/test.Script.js +++ b/test/test.Script.js @@ -1,12 +1,12 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); var ScriptModule = bitcore.Script; -var Address = bitcore.Address.class(); +var Address = bitcore.Address; var networks = bitcore.networks; var Script; var test_data = require('./testdata'); @@ -16,7 +16,7 @@ describe('Script', function() { should.exist(ScriptModule); }); it('should be able to create class', function() { - Script = ScriptModule.class(); + Script = ScriptModule; should.exist(Script); }); it('should be able to create instance', function() { diff --git a/test/test.ScriptInterpreter.js b/test/test.ScriptInterpreter.js index 909a427..e6d1746 100644 --- a/test/test.ScriptInterpreter.js +++ b/test/test.ScriptInterpreter.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('ScriptInterpreter', function() { should.exist(ScriptInterpreterModule); }); it('should be able to create class', function() { - ScriptInterpreter = ScriptInterpreterModule.class(); + ScriptInterpreter = ScriptInterpreterModule; should.exist(ScriptInterpreter); }); it('should be able to create instance', function() { diff --git a/test/test.Transaction.js b/test/test.Transaction.js index c763760..24d9d6c 100644 --- a/test/test.Transaction.js +++ b/test/test.Transaction.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -9,7 +9,7 @@ var TransactionModule = bitcore.Transaction; var Transaction; var In; var Out; -var Script = bitcore.Script.class(); +var Script = bitcore.Script; var buffertools = require('buffertools'); var test_data = require('./testdata'); @@ -18,7 +18,7 @@ describe('Transaction', function() { should.exist(TransactionModule); }); it('should be able to create class', function() { - Transaction = TransactionModule.class(); + Transaction = TransactionModule; should.exist(Transaction); In = Transaction.In; Out = Transaction.Out; diff --git a/test/test.VersionedData.js b/test/test.VersionedData.js index 0f18360..b554ea3 100644 --- a/test/test.VersionedData.js +++ b/test/test.VersionedData.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('VersionedData', function() { should.exist(VersionedDataModule); }); it('should be able to create class', function() { - VersionedData = VersionedDataModule.class(); + VersionedData = VersionedDataModule; should.exist(VersionedData); }); it('should be able to create an instance', function() { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 906603e..f589662 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -13,7 +13,7 @@ describe('Wallet', function() { should.exist(WalletModule); }); it('should be able to create class', function() { - Wallet = WalletModule.class(); + Wallet = WalletModule; should.exist(Wallet); }); it('should be able to create instance', function() { diff --git a/test/test.WalletKey.js b/test/test.WalletKey.js index 77fef38..eb29ce0 100644 --- a/test/test.WalletKey.js +++ b/test/test.WalletKey.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); @@ -14,7 +14,7 @@ describe('WalletKey', function() { should.exist(WalletKeyModule); }); it('should be able to create class', function() { - WalletKey = WalletKeyModule.class(); + WalletKey = WalletKeyModule; should.exist(WalletKey); }); it('should be able to create instance', function() { diff --git a/test/test.basic.js b/test/test.basic.js index 4863160..e728542 100644 --- a/test/test.basic.js +++ b/test/test.basic.js @@ -1,15 +1,27 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); + +var test_data; +if (typeof dataValid !== 'undefined' ) { + test_data = { + dataValid: dataValid, + dataInvalid: dataInvalid, + }; + +} +else { + test_data = require('./testdata'); +} + var should = chai.should(); -var Address = bitcore.Address.class(); -var PrivateKey = bitcore.PrivateKey.class(); +var Address = bitcore.Address; +var PrivateKey = bitcore.PrivateKey; var networks = bitcore.networks; var KeyModule = bitcore.KeyModule; -var test_data = require('./testdata'); function test_encode_priv(b58, payload, isTestnet, isCompressed) { diff --git a/test/test.main.js b/test/test.main.js index aa6e8a2..ea4a9bc 100644 --- a/test/test.main.js +++ b/test/test.main.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var expect = chai.expect; var should = chai.should(); diff --git a/test/test.misc.js b/test/test.misc.js index bceebc2..2eb1ad9 100644 --- a/test/test.misc.js +++ b/test/test.misc.js @@ -1,7 +1,7 @@ 'use strict'; -var chai = require('chai'); -var bitcore = require('../bitcore'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var should = chai.should(); diff --git a/test/test.util.js b/test/test.util.js index 48294b3..f129323 100644 --- a/test/test.util.js +++ b/test/test.util.js @@ -1,5 +1,6 @@ -var chai = require('chai'); -var bitcore = require('../bitcore'); + +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); var coinUtil = bitcore.util; var should = chai.should(); var buffertools = require('buffertools'); diff --git a/test/testdata.js b/test/testdata.js index 9d62e9d..e9a01cf 100644 --- a/test/testdata.js +++ b/test/testdata.js @@ -1,5 +1,3 @@ - - var fs = require('fs'); var dataValid = JSON.parse(fs.readFileSync('test/data/base58_keys_valid.json')); diff --git a/util/BinaryParser.js b/util/BinaryParser.js index d0707e7..97187ea 100644 --- a/util/BinaryParser.js +++ b/util/BinaryParser.js @@ -2,147 +2,145 @@ * Simple synchronous parser based on node-binary. */ -function spec(b) { - function Parser(buffer) - { - this.subject = buffer; - this.pos = 0; - }; +var imports = require('soop').imports(); +function Parser(buffer) +{ + this.subject = buffer; + this.pos = 0; +}; - Parser.prototype.buffer = function buffer(len) { - var buf = this.subject.slice(this.pos, this.pos+len); - this.pos += len; - return buf; - }; +Parser.prototype.buffer = function buffer(len) { + var buf = this.subject.slice(this.pos, this.pos+len); + this.pos += len; + return buf; +}; - Parser.prototype.search = function search(needle) { - var len; +Parser.prototype.search = function search(needle) { + var len; - if ("string" === typeof needle || Buffer.isBuffer(needle)) { - // TODO: Slicing is probably too slow - len = this.subject.slice(this.pos).indexOf(needle); - if (len !== -1) { - this.pos += len + needle.length; - } - return len; + if ("string" === typeof needle || Buffer.isBuffer(needle)) { + // TODO: Slicing is probably too slow + len = this.subject.slice(this.pos).indexOf(needle); + if (len !== -1) { + this.pos += len + needle.length; } - if ("number" === typeof needle) { - needle = needle & 0xff; - // Search for single byte - for (var i = this.pos, l = this.subject.length; i < l; i++) { - if (this.subject[i] == needle) { - len = i - this.pos; - this.pos = i+1; - return len; - } + return len; + } + if ("number" === typeof needle) { + needle = needle & 0xff; + // Search for single byte + for (var i = this.pos, l = this.subject.length; i < l; i++) { + if (this.subject[i] == needle) { + len = i - this.pos; + this.pos = i+1; + return len; } - return -1; } - }; + return -1; + } +}; - /** - * Like search(), but returns the skipped bytes - */ - Parser.prototype.scan = function scan(needle) { - var startPos = this.pos; - var len = this.search(needle); - if (len !== -1) { - return this.subject.slice(startPos, startPos+len); - } else { - throw new Error('No match'); - } - }; +/** + * Like search(), but returns the skipped bytes + */ +Parser.prototype.scan = function scan(needle) { + var startPos = this.pos; + var len = this.search(needle); + if (len !== -1) { + return this.subject.slice(startPos, startPos+len); + } else { + throw new Error('No match'); + } +}; - Parser.prototype.eof = function eof() { - return this.pos >= this.subject.length; - }; +Parser.prototype.eof = function eof() { + return this.pos >= this.subject.length; +}; - // convert byte strings to unsigned little endian numbers - function decodeLEu (bytes) { - var acc = 0; - for (var i = 0; i < bytes.length; i++) { - acc += Math.pow(256,i) * bytes[i]; - } - return acc; +// convert byte strings to unsigned little endian numbers +function decodeLEu (bytes) { + var acc = 0; + for (var i = 0; i < bytes.length; i++) { + acc += Math.pow(256,i) * bytes[i]; } - - // convert byte strings to unsigned big endian numbers - function decodeBEu (bytes) { - var acc = 0; - for (var i = 0; i < bytes.length; i++) { - acc += Math.pow(256, bytes.length - i - 1) * bytes[i]; - } - return acc; + return acc; +} + +// convert byte strings to unsigned big endian numbers +function decodeBEu (bytes) { + var acc = 0; + for (var i = 0; i < bytes.length; i++) { + acc += Math.pow(256, bytes.length - i - 1) * bytes[i]; } - - // convert byte strings to signed big endian numbers - function decodeBEs (bytes) { - var val = decodeBEu(bytes); - if ((bytes[0] & 0x80) == 0x80) { - val -= Math.pow(256, bytes.length); - } - return val; + return acc; +} + +// convert byte strings to signed big endian numbers +function decodeBEs (bytes) { + var val = decodeBEu(bytes); + if ((bytes[0] & 0x80) == 0x80) { + val -= Math.pow(256, bytes.length); } - - // convert byte strings to signed little endian numbers - function decodeLEs (bytes) { - var val = decodeLEu(bytes); - if ((bytes[bytes.length - 1] & 0x80) == 0x80) { - val -= Math.pow(256, bytes.length); - } - return val; + return val; +} + +// convert byte strings to signed little endian numbers +function decodeLEs (bytes) { + var val = decodeLEu(bytes); + if ((bytes[bytes.length - 1] & 0x80) == 0x80) { + val -= Math.pow(256, bytes.length); } + return val; +} - function getDecoder(len, fn) { - return function () { - var buf = this.buffer(len); - return fn(buf); - }; - }; - [ 1, 2, 4, 8 ].forEach(function (bytes) { - var bits = bytes * 8; - - Parser.prototype['word' + bits + 'le'] - = Parser.prototype['word' + bits + 'lu'] - = getDecoder(bytes, decodeLEu); - - Parser.prototype['word' + bits + 'ls'] - = getDecoder(bytes, decodeLEs); - - Parser.prototype['word' + bits + 'be'] - = Parser.prototype['word' + bits + 'bu'] - = getDecoder(bytes, decodeBEu); - - Parser.prototype['word' + bits + 'bs'] - = getDecoder(bytes, decodeBEs); - - Parser.prototype.word8 = Parser.prototype.word8u = Parser.prototype.word8be; - Parser.prototype.word8s = Parser.prototype.word8bs; - }); - - Parser.prototype.varInt = function () - { - var firstByte = this.word8(); - switch (firstByte) { - case 0xFD: - return this.word16le(); - - case 0xFE: - return this.word32le(); - - case 0xFF: - return this.word64le(); - - default: - return firstByte; - } - }; - - Parser.prototype.varStr = function () { - var len = this.varInt(); - return this.buffer(len); +function getDecoder(len, fn) { + return function () { + var buf = this.buffer(len); + return fn(buf); }; +}; +[ 1, 2, 4, 8 ].forEach(function (bytes) { + var bits = bytes * 8; + + Parser.prototype['word' + bits + 'le'] + = Parser.prototype['word' + bits + 'lu'] + = getDecoder(bytes, decodeLEu); + + Parser.prototype['word' + bits + 'ls'] + = getDecoder(bytes, decodeLEs); + + Parser.prototype['word' + bits + 'be'] + = Parser.prototype['word' + bits + 'bu'] + = getDecoder(bytes, decodeBEu); + + Parser.prototype['word' + bits + 'bs'] + = getDecoder(bytes, decodeBEs); + + Parser.prototype.word8 = Parser.prototype.word8u = Parser.prototype.word8be; + Parser.prototype.word8s = Parser.prototype.word8bs; +}); + +Parser.prototype.varInt = function () +{ + var firstByte = this.word8(); + switch (firstByte) { + case 0xFD: + return this.word16le(); + + case 0xFE: + return this.word32le(); + + case 0xFF: + return this.word64le(); + + default: + return firstByte; + } +}; - return Parser; +Parser.prototype.varStr = function () { + var len = this.varInt(); + return this.buffer(len); }; -module.defineClass(spec); + +module.exports = require('soop')(Parser); diff --git a/util/EncodedData.js b/util/EncodedData.js index 05557fa..2736fc1 100644 --- a/util/EncodedData.js +++ b/util/EncodedData.js @@ -1,159 +1,158 @@ -require('classtool'); - -function ClassSpec(b) { - var base58 = b.base58 || require('base58-native').base58Check; - - // Constructor. Takes the following forms: - // new EncodedData() - // new EncodedData() - // new EncodedData(, ) - // new EncodedData(, <20-byte-hash>) - function EncodedData(data, encoding) { - this.data = data; - if(!encoding && (typeof data == 'string')) { - this.__proto__ = this.encodings['base58']; - } else { - this.__proto__ = this.encodings[encoding || 'binary']; - } - }; +var imports = require('soop').imports(); +var base58 = imports.base58 || require('base58-native').base58Check; + + +// Constructor. Takes the following forms: +// new EncodedData() +// new EncodedData() +// new EncodedData(, ) +// new EncodedData(, <20-byte-hash>) +function EncodedData(data, encoding) { + this.data = data; + if(!encoding && (typeof data == 'string')) { + this.__proto__ = this.encodings['base58']; + } else { + this.__proto__ = this.encodings[encoding || 'binary']; + } +}; - // get or set the encoding used (transforms data) - EncodedData.prototype.encoding = function(encoding) { - if(encoding && (encoding != this._encoding)) { - this.data = this.as(encoding); - this.__proto__ = this.encodings[encoding]; - } - return this._encoding; - }; - - // answer a new instance having the given encoding - EncodedData.prototype.withEncoding = function(encoding) { - return new EncodedData(this.as(encoding), encoding); - }; - - // answer the data in the given encoding - EncodedData.prototype.as = function(encoding) { - if(!encodings[encoding]) throw new Error('invalid encoding'); - return this.converters[encoding].call(this); - }; - - // validate that we can convert to binary - EncodedData.prototype._validate = function() { - this.withEncoding('binary'); - }; - - // Boolean protocol for testing if valid - EncodedData.prototype.isValid = function() { - try { - this.validate(); - return true; - } catch(e) { - return false; - } - }; - - // subclasses can override to do more stuff - EncodedData.prototype.validate = function() { - this._validate(); - }; - - // Boolean protocol for testing if valid - EncodedData.prototype.isValid = function() { - try { - this.validate(); - return true; - } catch(e) { - return false; - } - }; - - // convert to a string (in base58 form) - EncodedData.prototype.toString = function() { - return this.as('base58'); - }; - - // utility - EncodedData.prototype.doAsBinary = function(callback) { - var oldEncoding = this.encoding(); - this.encoding('binary'); - callback.apply(this); - this.encoding(oldEncoding); - }; - - // Setup support for various address encodings. The object for - // each encoding inherits from the EncodedData prototype. This - // allows any encoding to override any method...changing the encoding - // for an instance will change the encoding it inherits from. Note, - // this will present some problems for anyone wanting to inherit from - // EncodedData (we'll deal with that when needed). - var encodings = { - 'binary': { - converters: { - 'binary': function() { - var answer = new Buffer(this.data.length); - this.data.copy(answer); - return answer; - }, - 'base58': function() { - return base58.encode(this.data); - }, - 'hex': function() { - return this.data.toString('hex'); - }, +// get or set the encoding used (transforms data) +EncodedData.prototype.encoding = function(encoding) { + if(encoding && (encoding != this._encoding)) { + this.data = this.as(encoding); + this.__proto__ = this.encodings[encoding]; + } + return this._encoding; +}; + +// answer a new instance having the given encoding +EncodedData.prototype.withEncoding = function(encoding) { + return new EncodedData(this.as(encoding), encoding); +}; + +// answer the data in the given encoding +EncodedData.prototype.as = function(encoding) { + if(!encodings[encoding]) throw new Error('invalid encoding'); + return this.converters[encoding].call(this); +}; + +// validate that we can convert to binary +EncodedData.prototype._validate = function() { + this.withEncoding('binary'); +}; + +// Boolean protocol for testing if valid +EncodedData.prototype.isValid = function() { + try { + this.validate(); + return true; + } catch(e) { + return false; + } +}; + +// subclasses can override to do more stuff +EncodedData.prototype.validate = function() { + this._validate(); +}; + +// Boolean protocol for testing if valid +EncodedData.prototype.isValid = function() { + try { + this.validate(); + return true; + } catch(e) { + return false; + } +}; + +// convert to a string (in base58 form) +EncodedData.prototype.toString = function() { + return this.as('base58'); +}; + +// utility +EncodedData.prototype.doAsBinary = function(callback) { + var oldEncoding = this.encoding(); + this.encoding('binary'); + callback.apply(this); + this.encoding(oldEncoding); +}; + +// Setup support for various address encodings. The object for +// each encoding inherits from the EncodedData prototype. This +// allows any encoding to override any method...changing the encoding +// for an instance will change the encoding it inherits from. Note, +// this will present some problems for anyone wanting to inherit from +// EncodedData (we'll deal with that when needed). +var encodings = { + 'binary': { + converters: { + 'binary': function() { + var answer = new Buffer(this.data.length); + this.data.copy(answer); + return answer; }, - - _validate: function() { - //nothing to do here...we make no assumptions about the data + 'base58': function() { + return base58.encode(this.data); + }, + 'hex': function() { + return this.data.toString('hex'); }, }, - 'base58': { - converters: { - 'binary': function() { - return base58.decode(this.data); - }, - 'hex': function() { - return this.withEncoding('binary').as('hex'); - }, + _validate: function() { + //nothing to do here...we make no assumptions about the data + }, + }, + + 'base58': { + converters: { + 'binary': function() { + return base58.decode(this.data); + }, + 'hex': function() { + return this.withEncoding('binary').as('hex'); }, }, + }, - 'hex': { - converters: { - 'binary': function() { - return new Buffer(this.data, 'hex'); - }, - 'base58': function() { - return this.withEncoding('binary').as('base58'); - }, + 'hex': { + converters: { + 'binary': function() { + return new Buffer(this.data, 'hex'); + }, + 'base58': function() { + return this.withEncoding('binary').as('base58'); }, }, - }; + }, +}; + +var no_conversion = function() {return this.data;}; +for(var k in encodings) { + if(encodings.hasOwnProperty(k)){ + if(!encodings[k].converters[k]) + encodings[k].converters[k] = no_conversion; + encodings[k]._encoding = k; + } +} - var no_conversion = function() {return this.data;}; +EncodedData.applyEncodingsTo = function(aClass) { + var tmp = {}; for(var k in encodings) { - if(encodings.hasOwnProperty(k)){ - if(!encodings[k].converters[k]) - encodings[k].converters[k] = no_conversion; - encodings[k]._encoding = k; + var enc = encodings[k]; + var obj = {}; + for(var j in enc) { + obj[j] = enc[j]; } + obj.__proto__ = aClass.prototype; + tmp[k] = obj; } + aClass.prototype.encodings = tmp; +}; - EncodedData.applyEncodingsTo = function(aClass) { - var tmp = {}; - for(var k in encodings) { - var enc = encodings[k]; - var obj = {}; - for(var j in enc) { - obj[j] = enc[j]; - } - obj.__proto__ = aClass.prototype; - tmp[k] = obj; - } - aClass.prototype.encodings = tmp; - }; +EncodedData.applyEncodingsTo(EncodedData); + +module.exports = require('soop')(EncodedData); - EncodedData.applyEncodingsTo(EncodedData); - return EncodedData; -} -module.defineClass(ClassSpec); diff --git a/util/VersionedData.js b/util/VersionedData.js index b4317fd..ac75559 100644 --- a/util/VersionedData.js +++ b/util/VersionedData.js @@ -1,39 +1,38 @@ -require('classtool'); +var imports = require('soop').imports(); +var base58 = imports.base58 || require('base58-native').base58Check; +var parent = imports.parent || require('./EncodedData'); -function ClassSpec(b) { - var superclass = b.superclass || require('./EncodedData').class(); - function VersionedData(version, payload) { - if(typeof version != 'number') { - VersionedData.super(this, arguments); - return; - }; - this.data = new Buffer(payload.length + 1); - this.__proto__ = this.encodings['binary']; - this.version(version); - this.payload(payload); +function VersionedData(version, payload) { + if(typeof version != 'number') { + VersionedData.super(this, arguments); + return; }; - VersionedData.superclass = superclass; - superclass.applyEncodingsTo(VersionedData); + this.data = new Buffer(payload.length + 1); + this.__proto__ = this.encodings['binary']; + this.version(version); + this.payload(payload); +}; - // get or set the version data (the first byte of the address) - VersionedData.prototype.version = function(num) { - if(num || (num === 0)) { - this.doAsBinary(function() {this.data.writeUInt8(num, 0);}); - return num; - } - return this.as('binary').readUInt8(0); - }; +VersionedData.parent = parent; +parent.applyEncodingsTo(VersionedData); - // get or set the payload data (as a Buffer object) - VersionedData.prototype.payload = function(data) { - if(data) { - this.doAsBinary(function() {data.copy(this.data,1);}); - return data; - } - return this.as('binary').slice(1); - }; +// get or set the version data (the first byte of the address) +VersionedData.prototype.version = function(num) { + if(num || (num === 0)) { + this.doAsBinary(function() {this.data.writeUInt8(num, 0);}); + return num; + } + return this.as('binary').readUInt8(0); +}; - return VersionedData; +// get or set the payload data (as a Buffer object) +VersionedData.prototype.payload = function(data) { + if(data) { + this.doAsBinary(function() {data.copy(this.data,1);}); + return data; + } + return this.as('binary').slice(1); }; -module.defineClass(ClassSpec); + +module.exports = require('soop')(VersionedData);