From c0c325dabdcedcb69c0b18a48d56ab82a6dfcca9 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 5 Mar 2014 16:11:16 -0300 Subject: [PATCH] all classes working with soop and test passing --- Address.js | 37 +- Block.js | 1117 ++++++++++--------- Bloom.js | 221 ++-- Connection.js | 1017 +++++++++-------- Opcode.js | 315 +++--- Peer.js | 109 +- PeerManager.js | 419 ++++--- PrivateKey.js | 125 +-- RpcClient.js | 404 ++++--- SIN.js | 109 +- SINKey.js | 72 +- Script.js | 1028 +++++++++-------- ScriptInterpreter.js | 1916 ++++++++++++++++---------------- Transaction.js | 1500 +++++++++++++------------ Wallet.js | 272 +++-- WalletKey.js | 101 +- test/test.Address.js | 2 +- test/test.Block.js | 2 +- test/test.Bloom.js | 2 +- test/test.Connection.js | 2 +- test/test.EncodedData.js | 2 +- test/test.Opcode.js | 2 +- test/test.Peer.js | 2 +- test/test.PeerManager.js | 2 +- test/test.PrivateKey.js | 2 +- test/test.RpcClient.js | 2 +- test/test.SIN.js | 2 +- test/test.SINKey.js | 2 +- test/test.Script.js | 4 +- test/test.ScriptInterpreter.js | 2 +- test/test.Transaction.js | 2 +- test/test.VersionedData.js | 2 +- test/test.Wallet.js | 2 +- test/test.WalletKey.js | 2 +- test/test.basic.js | 4 +- util/BinaryParser.js | 284 +++-- util/EncodedData.js | 305 +++-- util/VersionedData.js | 70 +- 38 files changed, 4700 insertions(+), 4763 deletions(-) diff --git a/Address.js b/Address.js index e429a3192..f274333fc 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); - } - - Address.superclass = superclass; - superclass.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'); - }); - }; - - return Address; +function Address() { + Address.super(this, arguments); } -module.defineClass(ClassSpec); + +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'); + }); +}; + +module.exports = require('soop')(Address); diff --git a/Block.js b/Block.js index c1f5238e1..aeda5f989 100644 --- a/Block.js +++ b/Block.js @@ -1,594 +1,591 @@ -require('classtool'); +var imports = require('soop').imports(); -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 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.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; + + if (headerOnly) + return; + + var txCount = parser.varInt(); + + 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(); + + 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.getHash = function getHash() { + if (!this.hash || !this.hash.length) this.hash = this.calcHash(); + + return this.hash; +}; + +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); + + if (buffertools.compare(this.hash, target) > 0) { + throw new VerificationError('Difficulty target not met'); } - 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; - }; + // Return the hash to its normal order + buffertools.reverse(this.hash); - 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(); + return true; +}; - this.txs = []; - this.size = 0; +/** + * 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)); +}; - if (headerOnly) - return; +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'); + } - var txCount = parser.varInt(); + return true; +}; - for (var i = 0; i < txCount; i++) { - var tx = new Transaction(); - tx.parse(parser); - this.txs.push(tx); +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.calcHash = function calcHash() { - var header = this.getHeader(); + return true; +}; - return util.twoSha256(header); - }; +/** + * 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(). - Block.prototype.checkHash = function checkHash() { - if (!this.hash || !this.hash.length) return false; - return buffertools.compare(this.calcHash(), this.hash) == 0; - }; + if (txs.length == 0) { + return [util.NULL_HASH.slice(0)]; + } - Block.prototype.getHash = function getHash() { - if (!this.hash || !this.hash.length) this.hash = this.calcHash(); + // 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; + }); - return this.hash; - }; - - 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); - - if (buffertools.compare(this.hash, target) > 0) { - throw new VerificationError('Difficulty target not met'); + 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 the hash to its normal order - buffertools.reverse(this.hash); + return tree; +}; - return true; - }; +Block.prototype.calcMerkleRoot = function calcMerkleRoot(txs) { + var tree = this.getMerkleTree(txs); + return tree[tree.length - 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.checkMerkleRoot = function checkMerkleRoot(txs) { + if (!this.merkle_root || !this.merkle_root.length) { + throw new VerificationError('No merkle root'); + } - 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'); + if (buffertools.compare(this.calcMerkleRoot(), this.merkle_root) == 0) { + throw new VerificationError('Merkle root incorrect'); + } + + return true; +}; + +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"); } + } + 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.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.getBlockValue = function getBlockValue() { + return Block.getBlockValue(this.height); +}; + +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())); +}; + +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; +}; + +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; +}; + +/** + * 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 targetTimespan = blockChain.getTargetTimespan(); + var targetSpacing = blockChain.getTargetSpacing(); + var interval = targetTimespan / targetSpacing; + + 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 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 { + // 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); + } + }; + lookForLastNonMinDiff(this, callback); + } } - } - - 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)]; - } - - // 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; - }; - - 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'); - } - - if (buffertools.compare(this.calcMerkleRoot(), this.merkle_root) == 0) { - throw new VerificationError('Merkle root incorrect'); - } - - return true; - }; - - 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"); - } - } - 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.prototype.getBlockValue = function getBlockValue() { - return Block.getBlockValue(this.height); - }; - - 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())); - }; - - 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; - }; - - 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; - }; - - /** - * 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 targetTimespan = blockChain.getTargetTimespan(); - var targetSpacing = blockChain.getTargetSpacing(); - var interval = targetTimespan / targetSpacing; - - if (this.height == 0) { + // 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; - if ((this.height+1) % interval !== 0) { - if (blockChain.isTestnet()) { - // Special testnet difficulty rules - var lastBlock = blockChain.getTopBlock(); + // Determine how long the difficulty period really took + var actualTimespan = self.timestamp - lastBlock.timestamp; - // 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 { - // 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); - } - }; - lookForLastNonMinDiff(this, callback); + // 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); + + 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')); + + callback(null, util.encodeDiffBits(newTarget)); + } catch (err) { + callback(err); } - } 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; - } - - var oldTarget = util.decodeDiffBits(self.bits, true); - var newTarget = oldTarget.mul(actualTimespan).div(targetTimespan); - - 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')); - - callback(null, util.encodeDiffBits(newTarget)); - } catch (err) { - callback(err); - } - } - ); - } - }; - - var medianTimeSpan = 11; - - 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); - } - 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; - }); - - // Sort timestamps - timestamps = timestamps.sort(); - - // 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+"'."); - } - - this(); - }, - function getMinimumTimestamp(err) { - 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"); - } - - 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.prepareNextBlock = - function prepareNextBlock(blockChain, beneficiary, time, callback) - { - var self = this; - - var newBlock = new Block(); - Step( - function getMedianTimePastStep() { - self.getMedianTimePast(blockChain, this); - }, - - 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)); - } - - self.getNextWork(blockChain, newBlock, this); - }, - - function applyNextWorkStep(err, nextWork) { - if (err) throw err; - newBlock.bits = nextWork; - this(null); - }, - - 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; - - // Create coinbase transaction - var txs = []; - - var tx = newBlock.createCoinbaseTx(beneficiary); - txs.push(tx); - - newBlock.merkle_root = newBlock.calcMerkleRoot(txs); - - // 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; - - var newBlock = data.block; - var txs = data.txs; - - newBlock.solve(miner, function (err, nonce) { - newBlock.nonce = nonce; - - // Make sure hash is cached - newBlock.getHash(); - - callback(err, newBlock, txs); - }); - - // 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); - }; - - /** - * 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; - }; - - return Block; + } }; -module.defineClass(spec); + +var medianTimeSpan = 11; + +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); + } + 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; + }); + + // Sort timestamps + timestamps = timestamps.sort(); + + // 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+"'."); + } + + this(); + }, + function getMinimumTimestamp(err) { + 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"); + } + + 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.prepareNextBlock = +function prepareNextBlock(blockChain, beneficiary, time, callback) +{ + var self = this; + + var newBlock = new Block(); + Step( + function getMedianTimePastStep() { + self.getMedianTimePast(blockChain, this); + }, + + 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)); + } + + self.getNextWork(blockChain, newBlock, this); + }, + + function applyNextWorkStep(err, nextWork) { + if (err) throw err; + newBlock.bits = nextWork; + this(null); + }, + + 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; + + // Create coinbase transaction + var txs = []; + + var tx = newBlock.createCoinbaseTx(beneficiary); + txs.push(tx); + + newBlock.merkle_root = newBlock.calcMerkleRoot(txs); + + // 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; + + var newBlock = data.block; + var txs = data.txs; + + newBlock.solve(miner, function (err, nonce) { + newBlock.nonce = nonce; + + // Make sure hash is cached + newBlock.getHash(); + + callback(err, newBlock, txs); + }); + + // 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); +}; + +/** + * 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; +}; + +module.exports = require('soop')(Block); diff --git a/Bloom.js b/Bloom.js index d514053d3..0bb166b6f 100644 --- a/Bloom.js +++ b/Bloom.js @@ -1,116 +1,111 @@ -require('classtool'); +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 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; - } - - 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); - }; - - return Bloom; +function Bloom() { + this.data = ''; + this.hashFuncs = 0; }; -module.defineClass(ClassSpec); +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; +} + +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 4672d16a4..f273ec3e7 100644 --- a/Connection.js +++ b/Connection.js @@ -1,547 +1,544 @@ -require('classtool'); +var imports = require('soop').imports(); -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 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 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 Binary = imports.Binary || require('binary'); +var Put = imports.Put || require('bufferput'); +var Buffers = imports.Buffers || require('buffers'); +require('./Buffers.monkey').patch(Buffers); - var BIP0031_VERSION = 60000; +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(); - function Connection(socket, peer) { - Connection.super(this, arguments); - this.socket = socket; - this.peer = peer; +var BIP0031_VERSION = 60000; - // 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; +function Connection(socket, peer) { + Connection.super(this, arguments); + this.socket = socket; + this.peer = peer; - // Receive buffer - this.buffers = new Buffers(); + // 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; - // 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; - } + // Receive buffer + this.buffers = new Buffers(); - this.setupHandlers(); + // 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)); - }; + this.setupHandlers(); +} +Connection.parent = imports.parent || require('events').EventEmitter; - Connection.prototype.handleConnect = function () { - if (!this.inbound) { - this.sendVersion(); - } - this.emit('connect', { - conn: this, - socket: this.socket, - peer: this.peer - }); - }; +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.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 - }); - }; +Connection.prototype.handleConnect = function () { + if (!this.inbound) { + this.sendVersion(); + } + this.emit('connect', { + conn: this, + socket: this.socket, + peer: this.peer + }); +}; - Connection.prototype.handleDisconnect = function () { - this.emit('disconnect', { - 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 + }); +}; - Connection.prototype.handleMessage = function(message) { - if (!message) { - // Parser was unable to make sense of the message, drop it - return; - } +Connection.prototype.handleDisconnect = function () { + this.emit('disconnect', { + conn: this, + socket: this.socket, + peer: this.peer + }); +}; - try { - switch (message.command) { - case 'version': - // Did we connect to ourself? - if (buffertools.compare(nonce, message.nonce) === 0) { - this.socket.end(); - return; - } +Connection.prototype.handleMessage = function(message) { + if (!message) { + // Parser was unable to make sense of the message, drop it + return; + } - if (this.inbound) { - this.sendVersion(); - } + try { + switch (message.command) { + case 'version': + // Did we connect to ourself? + if (buffertools.compare(nonce, message.nonce) === 0) { + this.socket.end(); + return; + } - 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; + if (this.inbound) { + this.sendVersion(); + } - case 'verack': + 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); - 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.sendPong = function (nonce) { - this.sendMessage('pong', nonce); - }; - - 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.sendGetBlocks = function (starts, stop, wantHeaders) { - var put = new Put(); - put.word32le(this.sendVer); - - put.varint(starts.length); - for (var i = 0; i < starts.length; i++) { - if (starts[i].length != 32) { - throw new Error('Invalid hash length'); - } - - put.put(starts[i]); - } - - var stopBuffer = new Buffer(stop, 'binary'); - if (stopBuffer.length != 32) { - throw new Error('Invalid hash length'); - } - - put.put(stopBuffer); - - var command = 'getblocks'; - if (wantHeaders) - command = 'getheaders'; - this.sendMessage(command, put.buffer()); - }; - - Connection.prototype.sendGetHeaders = function(starts, stop) { - this.sendGetBlocks(starts, stop, true); - }; - - 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()); - }; - - 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 + // 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)); } - put.put(value.getHash()); - }); - this.sendMessage('inv', put.buffer()); - }; - - Connection.prototype.sendHeaders = function (headers) { - var put = new Put(); - put.varint(headers.length); - headers.forEach(function (header) { - put.put(header); - - // Indicate 0 transactions - put.word8(0); - }); - this.sendMessage('headers', put.buffer()); - }; - - Connection.prototype.sendTx = function (tx) { - this.sendMessage('tx', tx.serialize()); - }; - - Connection.prototype.sendBlock = function (block, txs) { - var put = new Put(); - - // Block header - put.put(block.getHeader()); - - // List of transactions - put.varint(txs.length); - txs.forEach(function (tx) { - put.put(tx.serialize()); - }); - - this.sendMessage('block', 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'; - - var checksum; - if (this.sendVer >= 209) { - checksum = doubleSha256(payload).slice(0, 4); - } else { - checksum = new 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.handleData = function (data) { - this.buffers.push(data); - - 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; - } - - this.processData(); - }; - - 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)); - - this.buffers.skip(i); - } - break; - } - - if (i > (this.buffers.length - 4)) { - this.buffers.skip(i); - return; - } - i++; - } - - var payloadLen = (this.buffers.get(16) ) + - (this.buffers.get(17) << 8) + - (this.buffers.get(18) << 16) + - (this.buffers.get(19) << 24); - - var startPos = (this.recvVer >= 209) ? 24 : 20; - var endPos = startPos + payloadLen; - - if (this.buffers.length < endPos) 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; - - log.debug('['+this.peer+'] ' + - "Received message " + command + - " (" + payloadLen + " bytes)"); - - 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; - } - } - - 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); - } - - this.buffers.skip(endPos); - this.processData(); - }; - - Connection.prototype.parseMessage = function (command, payload) { - var parser = new Parser(payload); - - var data = { - command: command - }; - - 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(); + this.bestHeight = message.start_height; 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; - - case 'headers': - data.count = parser.varInt(); - - 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 '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; - - case 'addr': - var addrCount = parser.varInt(); - - // Enforce a maximum number of addresses per message - if (addrCount > 1000) { - addrCount = 1000; - } - - 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; - - case 'alert': - data.payload = parser.varStr(); - data.signature = parser.varStr(); + case 'verack': + this.recvVer = Math.min(message.version, PROTOCOL_VERSION); + this.active = true; break; case 'ping': - if (this.recvVer > BIP0031_VERSION) { - data.nonce = parser.buffer(8); + 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 + }); +}; - case 'getaddr': - case 'verack': - case 'reject': - // Empty message, nothing to parse - break; +Connection.prototype.sendPong = function (nonce) { + this.sendMessage('pong', nonce); +}; - default: - log.err('Connection.parseMessage(): Command not implemented', - {cmd: command}); +Connection.prototype.sendVersion = function () { + var subversion = '/BitcoinX:0.1/'; - // This tells the calling function not to issue an event - return null; + 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.sendGetBlocks = function (starts, stop, wantHeaders) { + var put = new Put(); + put.word32le(this.sendVer); + + put.varint(starts.length); + for (var i = 0; i < starts.length; i++) { + if (starts[i].length != 32) { + throw new Error('Invalid hash length'); } - return data; + put.put(starts[i]); + } + + var stopBuffer = new Buffer(stop, 'binary'); + if (stopBuffer.length != 32) { + throw new Error('Invalid hash length'); + } + + put.put(stopBuffer); + + var command = 'getblocks'; + if (wantHeaders) + command = 'getheaders'; + this.sendMessage(command, put.buffer()); +}; + +Connection.prototype.sendGetHeaders = function(starts, stop) { + this.sendGetBlocks(starts, stop, true); +}; + +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()); +}; + +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()); +}; + +Connection.prototype.sendHeaders = function (headers) { + var put = new Put(); + put.varint(headers.length); + headers.forEach(function (header) { + put.put(header); + + // Indicate 0 transactions + put.word8(0); + }); + this.sendMessage('headers', put.buffer()); +}; + +Connection.prototype.sendTx = function (tx) { + this.sendMessage('tx', tx.serialize()); +}; + +Connection.prototype.sendBlock = function (block, txs) { + var put = new Put(); + + // Block header + put.put(block.getHeader()); + + // List of transactions + put.varint(txs.length); + txs.forEach(function (tx) { + put.put(tx.serialize()); + }); + + this.sendMessage('block', 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'; + + var checksum; + if (this.sendVer >= 209) { + checksum = doubleSha256(payload).slice(0, 4); + } else { + checksum = new 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.handleData = function (data) { + this.buffers.push(data); + + 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; + } + + this.processData(); +}; + +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)); + + this.buffers.skip(i); + } + break; + } + + if (i > (this.buffers.length - 4)) { + this.buffers.skip(i); + return; + } + i++; + } + + var payloadLen = (this.buffers.get(16) ) + + (this.buffers.get(17) << 8) + + (this.buffers.get(18) << 16) + + (this.buffers.get(19) << 24); + + var startPos = (this.recvVer >= 209) ? 24 : 20; + var endPos = startPos + payloadLen; + + if (this.buffers.length < endPos) 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; + + log.debug('['+this.peer+'] ' + + "Received message " + command + + " (" + payloadLen + " bytes)"); + + 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; + } + } + + 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); + } + + this.buffers.skip(endPos); + this.processData(); +}; + +Connection.prototype.parseMessage = function (command, payload) { + var parser = new Parser(payload); + + var data = { + command: command }; - return Connection; + 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; + + case 'headers': + data.count = parser.varInt(); + + 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 '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; + + case 'addr': + var addrCount = parser.varInt(); + + // Enforce a maximum number of addresses per message + if (addrCount > 1000) { + addrCount = 1000; + } + + 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; + + case 'alert': + data.payload = parser.varStr(); + data.signature = parser.varStr(); + break; + + case 'ping': + if (this.recvVer > BIP0031_VERSION) { + data.nonce = parser.buffer(8); + } + break; + + case 'getaddr': + case 'verack': + case 'reject': + // Empty message, nothing to parse + break; + + 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/Opcode.js b/Opcode.js index 5044ab3e1..5ae4bee82 100644 --- a/Opcode.js +++ b/Opcode.js @@ -1,161 +1,158 @@ -require('classtool'); +var imports = require('soop').imports(); -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); - } - } - - return Opcode; +function Opcode(num) { + this.code = num; }; -module.defineClass(spec); + +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); + } +} + +module.exports = require('soop')(Opcode); diff --git a/Peer.js b/Peer.js index 54d663574..05be302f9 100644 --- a/Peer.js +++ b/Peer.js @@ -1,61 +1,58 @@ -require('classtool'); +var imports = require('soop').imports(); -function spec(b) { - var Net = b.Net || require('net'); - var Binary = b.Binary || require('binary'); - var buffertools = b.buffertools || require('buffertools'); +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); +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; - }; - - 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.prototype.getHostAsBuffer = function () { - return new Buffer(this.host.split('.')); - }; - - Peer.prototype.toString = function () { - return this.host + ":" + this.port; - }; - - 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(); - }; - - return Peer; + this.services = (services) ? services : null; + this.lastSeen = 0; }; -module.defineClass(spec); + +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.prototype.getHostAsBuffer = function () { + return new Buffer(this.host.split('.')); +}; + +Peer.prototype.toString = function () { + return this.host + ":" + this.port; +}; + +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.exports = require('soop')(Peer); diff --git a/PeerManager.js b/PeerManager.js index de3803835..4dc88040c 100644 --- a/PeerManager.js +++ b/PeerManager.js @@ -1,215 +1,212 @@ -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() {}; +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 Connection = imports.Connection || + require('soop').load('./Connection', {config: config, network: network}); - GetAdjustedTime = b.GetAdjustedTime || function () { - // TODO: Implement actual adjustment - return Math.floor(new Date().getTime() / 1000); - }; +var Peer = imports.Peer || require('./Peer'); - function PeerManager() { - this.active = false; - this.timer = null; - - this.peers = []; - this.connections = []; - this.isConnected = false; - this.peerDiscovery = false; - - // Move these to the Node's settings object - this.interval = 5000; - this.minConnections = 8; - this.minKnownPeers = 10; - }; - PeerManager.superclass = b.superclass || require('events').EventEmitter; - - PeerManager.Connection = Connection; - - PeerManager.prototype.start = function() { - this.active = true; - if(!this.timer) { - this.timer = setInterval(this.checkStatus.bind(this), this.interval); - } - }; - - 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; - } - }; - - 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); - } - }.bind(this)); - if (e.message.addrs.length < 1000 ) { - e.conn.getaddr = false; - } - }; - - 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.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; - } - }; - - 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(); - } - } else { - return null; - } - }; - - PeerManager.prototype.getActiveConnections = function () { - return this.connections.slice(0); - }; - - return PeerManager; +GetAdjustedTime = imports.GetAdjustedTime || function () { + // TODO: Implement actual adjustment + return Math.floor(new Date().getTime() / 1000); }; -module.defineClass(spec); + +function PeerManager() { + this.active = false; + this.timer = null; + + this.peers = []; + this.connections = []; + this.isConnected = false; + this.peerDiscovery = false; + + // Move these to the Node's settings object + this.interval = 5000; + this.minConnections = 8; + this.minKnownPeers = 10; +}; + +PeerManager.parent = imports.parent || require('events').EventEmitter; +PeerManager.Connection = Connection; + +PeerManager.prototype.start = function() { + this.active = true; + if(!this.timer) { + this.timer = setInterval(this.checkStatus.bind(this), this.interval); + } +}; + +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; + } +}; + +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); + } + }.bind(this)); + if (e.message.addrs.length < 1000 ) { + e.conn.getaddr = false; + } +}; + +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.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; + } +}; + +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(); + } + } else { + return null; + } +}; + +PeerManager.prototype.getActiveConnections = function () { + return this.connections.slice(0); +}; + +module.exports = require('soop')(PeerManager); diff --git a/PrivateKey.js b/PrivateKey.js index decc9c79f..c9a811c79 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); - }; - - PrivateKey.superclass = superclass; - superclass.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'); - }); - }; - - // 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 { - 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; +//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); }; -module.defineClass(ClassSpec); + +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'); + }); +}; + +// 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 { + 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.exports = require('soop')(PrivateKey); diff --git a/RpcClient.js b/RpcClient.js index 38df00feb..54dff5852 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 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); - } - } - }; - - Script.prototype.isPushOnly = function () - { - for (var i = 0; i < this.chunks.length; i++) - if (!Buffer.isBuffer(this.chunks[i])) - return false; - - 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; - } - - 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; - } - - // 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 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; - } - }; - - 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.prototype.getStringContent = function (truncate, maxEl) - { - if (truncate === null) { - truncate = true; - } - - if ("undefined" === typeof maxEl) { - maxEl = 15; - } - - var script = ''; - for (var i = 0, l = this.chunks.length; i < l; i++) { - var chunk = this.chunks[i]; - - if (i > 0) { - script += " "; - } - - if (Buffer.isBuffer(chunk)) { - script += "0x"+util.formatBuffer(chunk, truncate ? null : 0); - } else { - script += Opcode.reverseMap[chunk]; - } - - if (maxEl && i > maxEl) { - script += " ..."; - break; - } - } - return script; - }; - - Script.prototype.toString = function (truncate, maxEl) - { - var script = "