...no longer relies on Manuel's repo hostig a version of "bignum" that actually
contained bignumber.js. This moves bignumber.js internally and removes bignum
from the browser build process. Also adds a bitcore.Bignum that links to the
right thing. In node, browser.Bignum is require('bignum'). And in the browser,
bitcore.Bignum is is now Bignumber.js (same as before ... except bignumber.js
is now inside bitcore).
632 lines
17 KiB
JavaScript
632 lines
17 KiB
JavaScript
var imports = require('soop').imports();
|
|
var config = imports.config || require('../config');
|
|
var log = imports.log || require('../util/log');
|
|
var Address = imports.Address || require('./Address');
|
|
var Script = imports.Script || require('./Script');
|
|
var ScriptInterpreter = imports.ScriptInterpreter || require('./ScriptInterpreter');
|
|
var util = imports.util || require('../util');
|
|
var bignum = imports.bignum || require('./Bignum');
|
|
var Put = imports.Put || require('bufferput');
|
|
var Parser = imports.Parser || require('../util/BinaryParser');
|
|
var Step = imports.Step || require('step');
|
|
var buffertools = imports.buffertools || require('buffertools');
|
|
var error = imports.error || require('../util/error');
|
|
var networks = imports.networks || require('../networks');
|
|
var WalletKey = imports.WalletKey || require('./WalletKey');
|
|
var PrivateKey = imports.PrivateKey || require('./PrivateKey');
|
|
|
|
var COINBASE_OP = Buffer.concat([util.NULL_HASH, new Buffer('FFFFFFFF', 'hex')]);
|
|
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
|
|
|
|
Transaction.COINBASE_OP = COINBASE_OP;
|
|
|
|
function TransactionIn(data) {
|
|
if ("object" !== typeof data) {
|
|
data = {};
|
|
}
|
|
if (data.o) {
|
|
this.o = data.o;
|
|
} else {
|
|
if (data.oTxHash && typeof data.oIndex !== 'undefined' && data.oIndex >= 0) {
|
|
var hash = new Buffer(data.oTxHash, 'hex');
|
|
hash = buffertools.reverse(hash);
|
|
var voutBuf = new Buffer(4);
|
|
voutBuf.writeUInt32LE(data.oIndex, 0);
|
|
this.o = Buffer.concat([hash, voutBuf]);
|
|
}
|
|
}
|
|
this.s = Buffer.isBuffer(data.s) ? data.s :
|
|
Buffer.isBuffer(data.script) ? data.script : util.EMPTY_BUFFER;
|
|
this.q = data.q ? data.q : data.sequence;
|
|
}
|
|
|
|
TransactionIn.MAX_SEQUENCE = 0xffffffff;
|
|
|
|
TransactionIn.prototype.getScript = function getScript() {
|
|
return new Script(this.s);
|
|
};
|
|
|
|
TransactionIn.prototype.isCoinBase = function isCoinBase() {
|
|
if (!this.o) return false;
|
|
|
|
//The new Buffer is for Firefox compatibility
|
|
return buffertools.compare(new Buffer(this.o), COINBASE_OP) === 0;
|
|
};
|
|
|
|
TransactionIn.prototype.serialize = function serialize() {
|
|
var slen = util.varIntBuf(this.s.length);
|
|
var qbuf = new Buffer(4);
|
|
qbuf.writeUInt32LE(this.q, 0);
|
|
|
|
var ret = Buffer.concat([this.o, slen, this.s, qbuf]);
|
|
return ret;
|
|
};
|
|
|
|
TransactionIn.prototype.getOutpointHash = function getOutpointHash() {
|
|
if ("undefined" !== typeof this.o.outHashCache) {
|
|
return this.o.outHashCache;
|
|
}
|
|
return this.o.outHashCache = this.o.slice(0, 32);
|
|
};
|
|
|
|
TransactionIn.prototype.getOutpointIndex = function getOutpointIndex() {
|
|
return (this.o[32]) +
|
|
(this.o[33] << 8) +
|
|
(this.o[34] << 16) +
|
|
(this.o[35] << 24);
|
|
};
|
|
|
|
TransactionIn.prototype.setOutpointIndex = function setOutpointIndex(n) {
|
|
this.o[32] = n & 0xff;
|
|
this.o[33] = n >> 8 & 0xff;
|
|
this.o[34] = n >> 16 & 0xff;
|
|
this.o[35] = n >> 24 & 0xff;
|
|
};
|
|
|
|
|
|
function TransactionOut(data) {
|
|
if ("object" !== typeof data) {
|
|
data = {};
|
|
}
|
|
this.v = data.v ? data.v : data.value;
|
|
this.s = data.s ? data.s : data.script;
|
|
};
|
|
|
|
TransactionOut.prototype.getValue = function getValue() {
|
|
return new Parser(this.v).word64lu();
|
|
};
|
|
|
|
TransactionOut.prototype.getScript = function getScript() {
|
|
return new Script(this.s);
|
|
};
|
|
|
|
TransactionOut.prototype.serialize = function serialize() {
|
|
var slen = util.varIntBuf(this.s.length);
|
|
return Buffer.concat([this.v, slen, this.s]);
|
|
};
|
|
|
|
function Transaction(data) {
|
|
if ("object" !== typeof data) {
|
|
data = {};
|
|
}
|
|
this.hash = data.hash || null;
|
|
this.version = data.version;
|
|
this.lock_time = data.lock_time;
|
|
this.ins = Array.isArray(data.ins) ? data.ins.map(function(data) {
|
|
var txin = new TransactionIn();
|
|
txin.s = data.s;
|
|
txin.q = data.q;
|
|
txin.o = data.o;
|
|
return txin;
|
|
}) : [];
|
|
this.outs = Array.isArray(data.outs) ? data.outs.map(function(data) {
|
|
var txout = new TransactionOut();
|
|
txout.v = data.v;
|
|
txout.s = data.s;
|
|
return txout;
|
|
}) : [];
|
|
if (data.buffer) this._buffer = data.buffer;
|
|
};
|
|
Transaction.In = TransactionIn;
|
|
Transaction.Out = TransactionOut;
|
|
|
|
Transaction.prototype.isCoinBase = function() {
|
|
return this.ins.length == 1 && this.ins[0].isCoinBase();
|
|
};
|
|
|
|
Transaction.prototype.isStandard = function isStandard() {
|
|
var i;
|
|
for (i = 0; i < this.ins.length; i++) {
|
|
if (this.ins[i].getScript().getInType() == "Strange") {
|
|
return false;
|
|
}
|
|
}
|
|
for (i = 0; i < this.outs.length; i++) {
|
|
if (this.outs[i].getScript().getOutType() == "Strange") {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
Transaction.prototype.serialize = function serialize() {
|
|
var bufs = [];
|
|
|
|
var buf = new Buffer(4);
|
|
buf.writeUInt32LE(this.version, 0);
|
|
bufs.push(buf);
|
|
|
|
bufs.push(util.varIntBuf(this.ins.length));
|
|
this.ins.forEach(function(txin) {
|
|
bufs.push(txin.serialize());
|
|
});
|
|
|
|
bufs.push(util.varIntBuf(this.outs.length));
|
|
this.outs.forEach(function(txout) {
|
|
bufs.push(txout.serialize());
|
|
});
|
|
|
|
var buf = new Buffer(4);
|
|
buf.writeUInt32LE(this.lock_time, 0);
|
|
bufs.push(buf);
|
|
|
|
this._buffer = Buffer.concat(bufs);
|
|
return this._buffer;
|
|
};
|
|
|
|
Transaction.prototype.getBuffer = function getBuffer() {
|
|
if (this._buffer) return this._buffer;
|
|
|
|
return this.serialize();
|
|
};
|
|
|
|
Transaction.prototype.calcHash = function calcHash() {
|
|
this.hash = util.twoSha256(this.getBuffer());
|
|
return this.hash;
|
|
};
|
|
|
|
Transaction.prototype.checkHash = function checkHash() {
|
|
if (!this.hash || !this.hash.length) return false;
|
|
|
|
return buffertools.compare(this.calcHash(), this.hash) === 0;
|
|
};
|
|
|
|
Transaction.prototype.getHash = function getHash() {
|
|
if (!this.hash || !this.hash.length) {
|
|
this.hash = this.calcHash();
|
|
}
|
|
return this.hash;
|
|
};
|
|
|
|
|
|
Transaction.prototype.calcNormalizedHash = function () {
|
|
this.normalizedHash = this.hashForSignature(new Script(),0, SIGHASH_ALL);
|
|
return this.normalizedHash;
|
|
};
|
|
|
|
|
|
Transaction.prototype.getNormalizedHash = function () {
|
|
if (!this.normalizedHash || !this.normalizedHash.length) {
|
|
this.normalizedHash = this.calcNormalizedHash();
|
|
}
|
|
return this.normalizedHash;
|
|
};
|
|
|
|
|
|
|
|
// convert encoded list of inputs to easy-to-use JS list-of-lists
|
|
Transaction.prototype.inputs = function inputs() {
|
|
var res = [];
|
|
for (var i = 0; i < this.ins.length; i++) {
|
|
var txin = this.ins[i];
|
|
var outHash = txin.getOutpointHash();
|
|
var outIndex = txin.getOutpointIndex();
|
|
res.push([outHash, outIndex]);
|
|
}
|
|
|
|
return res;
|
|
};
|
|
|
|
Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, opts, callback) {
|
|
var scriptSig = this.ins[n].getScript();
|
|
return ScriptInterpreter.verifyFull(
|
|
scriptSig,
|
|
scriptPubKey,
|
|
this, n, 0,
|
|
opts,
|
|
callback);
|
|
};
|
|
|
|
/**
|
|
* Returns an object containing all pubkey hashes affected by this transaction.
|
|
*
|
|
* The return object contains the base64-encoded pubKeyHash values as keys
|
|
* and the original pubKeyHash buffers as values.
|
|
*/
|
|
Transaction.prototype.getAffectedKeys = function getAffectedKeys(txCache) {
|
|
// TODO: Function won't consider results cached if there are no affected
|
|
// accounts.
|
|
if (!(this.affects && this.affects.length)) {
|
|
this.affects = [];
|
|
|
|
// Index any pubkeys affected by the outputs of this transaction
|
|
for (var i = 0, l = this.outs.length; i < l; i++) {
|
|
var txout = this.outs[i];
|
|
var script = txout.getScript();
|
|
|
|
var outPubKey = script.simpleOutPubKeyHash();
|
|
if (outPubKey) {
|
|
this.affects.push(outPubKey);
|
|
}
|
|
};
|
|
|
|
// Index any pubkeys affected by the inputs of this transaction
|
|
var txIndex = txCache.txIndex;
|
|
for (var i = 0, l = this.ins.length; i < l; i++) {
|
|
var txin = this.ins[i];
|
|
|
|
if (txin.isCoinBase()) continue;
|
|
|
|
// In the case of coinbase or IP transactions, the txin doesn't
|
|
// actually contain the pubkey, so we look at the referenced txout
|
|
// instead.
|
|
var outHash = txin.getOutpointHash();
|
|
var outIndex = txin.getOutpointIndex();
|
|
var outHashBase64 = outHash.toString('base64');
|
|
var fromTxOuts = txIndex[outHashBase64];
|
|
|
|
if (!fromTxOuts) {
|
|
throw new Error("Input not found!");
|
|
}
|
|
|
|
var txout = fromTxOuts[outIndex];
|
|
var script = txout.getScript();
|
|
|
|
var outPubKey = script.simpleOutPubKeyHash();
|
|
if (outPubKey) {
|
|
this.affects.push(outPubKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
var affectedKeys = {};
|
|
|
|
this.affects.forEach(function(pubKeyHash) {
|
|
affectedKeys[pubKeyHash.toString('base64')] = pubKeyHash;
|
|
});
|
|
|
|
return affectedKeys;
|
|
};
|
|
|
|
var OP_CODESEPARATOR = 171;
|
|
|
|
var SIGHASH_ALL = 1;
|
|
var SIGHASH_NONE = 2;
|
|
var SIGHASH_SINGLE = 3;
|
|
var SIGHASH_ANYONECANPAY = 0x80;
|
|
|
|
Transaction.SIGHASH_ALL = SIGHASH_ALL;
|
|
Transaction.SIGHASH_NONE = SIGHASH_NONE;
|
|
Transaction.SIGHASH_SINGLE = SIGHASH_SINGLE;
|
|
Transaction.SIGHASH_ANYONECANPAY = SIGHASH_ANYONECANPAY;
|
|
|
|
var TransactionSignatureSerializer = function(txTo, scriptCode, nIn, nHashType) {
|
|
this.txTo = txTo;
|
|
this.scriptCode = scriptCode;
|
|
this.nIn = nIn;
|
|
this.anyoneCanPay = !!(nHashType & SIGHASH_ANYONECANPAY);
|
|
var hashTypeMode = nHashType & 0x1f;
|
|
this.hashSingle = hashTypeMode === SIGHASH_SINGLE;
|
|
this.hashNone = hashTypeMode === SIGHASH_NONE;
|
|
this.bytes = new Put();
|
|
};
|
|
|
|
// serialize an output of txTo
|
|
TransactionSignatureSerializer.prototype.serializeOutput = function(nOutput) {
|
|
if (this.hashSingle && nOutput != this.nIn) {
|
|
// Do not lock-in the txout payee at other indices as txin
|
|
// ::Serialize(s, CTxOut(), nType, nVersion);
|
|
this.bytes.put(util.INT64_MAX);
|
|
this.bytes.varint(0);
|
|
} else {
|
|
//::Serialize(s, txTo.vout[nOutput], nType, nVersion);
|
|
var out = this.txTo.outs[nOutput];
|
|
this.bytes.put(out.v);
|
|
this.bytes.varint(out.s.length);
|
|
this.bytes.put(out.s);
|
|
}
|
|
};
|
|
|
|
// serialize the script
|
|
TransactionSignatureSerializer.prototype.serializeScriptCode = function() {
|
|
this.scriptCode.findAndDelete(OP_CODESEPARATOR);
|
|
this.bytes.varint(this.scriptCode.buffer.length);
|
|
this.bytes.put(this.scriptCode.buffer);
|
|
};
|
|
|
|
// serialize an input of txTo
|
|
TransactionSignatureSerializer.prototype.serializeInput = function(nInput) {
|
|
// In case of SIGHASH_ANYONECANPAY, only the input being signed is serialized
|
|
if (this.anyoneCanPay) nInput = this.nIn;
|
|
|
|
// Serialize the prevout
|
|
this.bytes.put(this.txTo.ins[nInput].o);
|
|
|
|
// Serialize the script
|
|
if (nInput !== this.nIn) {
|
|
// Blank out other inputs' signatures
|
|
this.bytes.varint(0);
|
|
} else {
|
|
this.serializeScriptCode();
|
|
}
|
|
// Serialize the nSequence
|
|
if (nInput !== this.nIn && (this.hashSingle || this.hashNone)) {
|
|
// let the others update at will
|
|
this.bytes.word32le(0);
|
|
} else {
|
|
this.bytes.word32le(this.txTo.ins[nInput].q);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
// serialize txTo for signature
|
|
TransactionSignatureSerializer.prototype.serialize = function() {
|
|
// serialize nVersion
|
|
this.bytes.word32le(this.txTo.version);
|
|
// serialize vin
|
|
var nInputs = this.anyoneCanPay ? 1 : this.txTo.ins.length;
|
|
this.bytes.varint(nInputs);
|
|
for (var nInput = 0; nInput < nInputs; nInput++) {
|
|
this.serializeInput(nInput);
|
|
}
|
|
// serialize vout
|
|
var nOutputs = this.hashNone ? 0 : (this.hashSingle ? this.nIn + 1 : this.txTo.outs.length);
|
|
this.bytes.varint(nOutputs);
|
|
for (var nOutput = 0; nOutput < nOutputs; nOutput++) {
|
|
this.serializeOutput(nOutput);
|
|
}
|
|
|
|
// serialize nLockTime
|
|
this.bytes.word32le(this.txTo.lock_time);
|
|
};
|
|
|
|
TransactionSignatureSerializer.prototype.buffer = function() {
|
|
this.serialize();
|
|
return this.bytes.buffer();
|
|
};
|
|
|
|
Transaction.Serializer = TransactionSignatureSerializer;
|
|
|
|
var oneBuffer = function() {
|
|
// bug present in bitcoind which must be also present in bitcore
|
|
// see https://bitcointalk.org/index.php?topic=260595
|
|
var ret = new Buffer(32);
|
|
ret.writeUInt8(1, 0);
|
|
for (var i=1; i<32; i++) ret.writeUInt8(0, i);
|
|
return ret; // return 1 bug
|
|
};
|
|
|
|
Transaction.prototype.hashForSignature =
|
|
function hashForSignature(script, inIndex, hashType) {
|
|
|
|
if (+inIndex !== inIndex ||
|
|
inIndex < 0 || inIndex >= this.ins.length) {
|
|
return oneBuffer();
|
|
}
|
|
// Check for invalid use of SIGHASH_SINGLE
|
|
var hashTypeMode = hashType & 0x1f;
|
|
if (hashTypeMode === SIGHASH_SINGLE) {
|
|
if (inIndex >= this.outs.length) {
|
|
return oneBuffer();
|
|
}
|
|
}
|
|
|
|
// Wrapper to serialize only the necessary parts of the transaction being signed
|
|
var serializer = new TransactionSignatureSerializer(this, script, inIndex, hashType);
|
|
// Serialize
|
|
var buffer = serializer.buffer();
|
|
// Append hashType
|
|
var hashBuf = new Put().word32le(hashType).buffer();
|
|
buffer = Buffer.concat([buffer, hashBuf]);
|
|
return util.twoSha256(buffer);
|
|
};
|
|
|
|
/**
|
|
* Returns an object with the same field names as jgarzik's getblock patch.
|
|
*/
|
|
Transaction.prototype.getStandardizedObject = function getStandardizedObject() {
|
|
var tx = {
|
|
hash: util.formatHashFull(this.getHash()),
|
|
version: this.version,
|
|
lock_time: this.lock_time
|
|
};
|
|
|
|
var totalSize = 8; // version + lock_time
|
|
totalSize += util.getVarIntSize(this.ins.length); // tx_in count
|
|
var ins = this.ins.map(function(txin) {
|
|
var txinObj = {
|
|
prev_out: {
|
|
hash: buffertools.reverse(new Buffer(txin.getOutpointHash())).toString('hex'),
|
|
n: txin.getOutpointIndex()
|
|
},
|
|
sequence: txin.q
|
|
};
|
|
if (txin.isCoinBase()) {
|
|
txinObj.coinbase = txin.s.toString('hex');
|
|
} else {
|
|
txinObj.scriptSig = new Script(txin.s).getStringContent(false, 0);
|
|
}
|
|
totalSize += 36 + util.getVarIntSize(txin.s.length) +
|
|
txin.s.length + 4; // outpoint + script_len + script + sequence
|
|
return txinObj;
|
|
});
|
|
|
|
totalSize += util.getVarIntSize(this.outs.length);
|
|
var outs = this.outs.map(function(txout) {
|
|
totalSize += util.getVarIntSize(txout.s.length) +
|
|
txout.s.length + 8; // script_len + script + value
|
|
return {
|
|
value: util.formatValue(txout.v),
|
|
scriptPubKey: new Script(txout.s).getStringContent(false, 0)
|
|
};
|
|
});
|
|
|
|
tx.size = totalSize;
|
|
|
|
tx["in"] = ins;
|
|
tx["out"] = outs;
|
|
|
|
return tx;
|
|
};
|
|
|
|
// Add some Mongoose compatibility functions to the plain object
|
|
Transaction.prototype.toObject = function toObject() {
|
|
return this;
|
|
};
|
|
|
|
Transaction.prototype.fromObj = function fromObj(obj) {
|
|
var txobj = {};
|
|
txobj.version = obj.version || 1;
|
|
txobj.lock_time = obj.lock_time || 0;
|
|
txobj.ins = [];
|
|
txobj.outs = [];
|
|
|
|
obj.inputs.forEach(function(inputobj) {
|
|
var txin = new TransactionIn();
|
|
txin.s = util.EMPTY_BUFFER;
|
|
txin.q = 0xffffffff;
|
|
|
|
var hash = new Buffer(inputobj.txid, 'hex');
|
|
hash = buffertools.reverse(hash);
|
|
var vout = parseInt(inputobj.vout);
|
|
var voutBuf = new Buffer(4);
|
|
voutBuf.writeUInt32LE(vout, 0);
|
|
|
|
txin.o = Buffer.concat([hash, voutBuf]);
|
|
|
|
txobj.ins.push(txin);
|
|
});
|
|
|
|
var keys = Object.keys(obj.outputs);
|
|
keys.forEach(function(addrStr) {
|
|
var addr = new Address(addrStr);
|
|
var script = Script.createPubKeyHashOut(addr.payload());
|
|
|
|
var valueNum = bignum(obj.outputs[addrStr]);
|
|
var value = util.bigIntToValue(valueNum);
|
|
|
|
var txout = new TransactionOut();
|
|
txout.v = value;
|
|
txout.s = script.getBuffer();
|
|
|
|
txobj.outs.push(txout);
|
|
});
|
|
|
|
this.lock_time = txobj.lock_time;
|
|
this.version = txobj.version;
|
|
this.ins = txobj.ins;
|
|
this.outs = txobj.outs;
|
|
}
|
|
|
|
Transaction.prototype.parse = function(parser) {
|
|
if (Buffer.isBuffer(parser)) {
|
|
this._buffer = parser;
|
|
parser = new Parser(parser);
|
|
}
|
|
|
|
var i, sLen, startPos = parser.pos;
|
|
|
|
this.version = parser.word32le();
|
|
|
|
var txinCount = parser.varInt();
|
|
|
|
this.ins = [];
|
|
for (j = 0; j < txinCount; j++) {
|
|
var txin = new TransactionIn();
|
|
txin.o = parser.buffer(36); // outpoint
|
|
sLen = parser.varInt(); // script_len
|
|
txin.s = parser.buffer(sLen); // script
|
|
txin.q = parser.word32le(); // sequence
|
|
this.ins.push(txin);
|
|
}
|
|
|
|
var txoutCount = parser.varInt();
|
|
|
|
this.outs = [];
|
|
for (j = 0; j < txoutCount; j++) {
|
|
var txout = new TransactionOut();
|
|
txout.v = parser.buffer(8); // value
|
|
sLen = parser.varInt(); // script_len
|
|
txout.s = parser.buffer(sLen); // script
|
|
this.outs.push(txout);
|
|
}
|
|
|
|
this.lock_time = parser.word32le();
|
|
this.calcHash();
|
|
};
|
|
|
|
|
|
|
|
|
|
Transaction.prototype.calcSize = function() {
|
|
var totalSize = 8; // version + lock_time
|
|
totalSize += util.getVarIntSize(this.ins.length); // tx_in count
|
|
this.ins.forEach(function(txin) {
|
|
totalSize += 36 + util.getVarIntSize(txin.s.length) +
|
|
txin.s.length + 4; // outpoint + script_len + script + sequence
|
|
});
|
|
|
|
totalSize += util.getVarIntSize(this.outs.length);
|
|
this.outs.forEach(function(txout) {
|
|
totalSize += util.getVarIntSize(txout.s.length) +
|
|
txout.s.length + 8; // script_len + script + value
|
|
});
|
|
this.size = totalSize;
|
|
return totalSize;
|
|
};
|
|
|
|
Transaction.prototype.getSize = function () {
|
|
if (!this.size) {
|
|
this.size = this.calcSize();
|
|
}
|
|
return this.size;
|
|
};
|
|
|
|
Transaction.prototype.countInputSignatures = function(index) {
|
|
var ret = 0;
|
|
var script = new Script(this.ins[index].s);
|
|
return script.countSignatures();
|
|
};
|
|
|
|
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
|
|
Transaction.prototype.countInputMissingSignatures = function(index) {
|
|
var ret = 0;
|
|
var script = new Script(this.ins[index].s);
|
|
return script.countMissingSignatures();
|
|
};
|
|
|
|
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
|
|
Transaction.prototype.isInputComplete = function(index) {
|
|
var m = this.countInputMissingSignatures(index);
|
|
if (m===null) return null;
|
|
return m === 0;
|
|
};
|
|
|
|
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
|
|
Transaction.prototype.isComplete = function() {
|
|
var ret = true;
|
|
var l = this.ins.length;
|
|
|
|
for (var i = 0; i < l; i++) {
|
|
if (!this.isInputComplete(i)){
|
|
ret = false;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
|
|
module.exports = require('soop')(Transaction);
|