Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 8 additions & 187 deletions src/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ var BigInteger = require('./jsbn/jsbn');
var Script = require('./script');
var util = require('./util');
var convert = require('./convert');
var Wallet = require('./wallet');
var ECKey = require('./eckey').ECKey;
var ECDSA = require('./ecdsa');
var Address = require('./address');
Expand All @@ -15,8 +14,7 @@ var Transaction = function (doc) {
this.locktime = 0;
this.ins = [];
this.outs = [];
this.timestamp = null;
this.block = null;
this.defaultSequence = [255, 255, 255, 255] // 0xFFFFFFFF

if (doc) {
if (typeof doc == "string" || Array.isArray(doc)) {
Expand All @@ -35,27 +33,11 @@ var Transaction = function (doc) {
this.addOutput(new TransactionOut(doc.outs[i]));
}
}
if (doc.timestamp) this.timestamp = doc.timestamp;
if (doc.block) this.block = doc.block;

this.hash = this.hash || this.getHash()
}
};

/**
* Turn transaction data into Transaction objects.
*
* Takes an array of plain JavaScript objects containing transaction data and
* returns an array of Transaction objects.
*/
Transaction.objectify = function (txs) {
var objs = [];
for (var i = 0; i < txs.length; i++) {
objs.push(new Transaction(txs[i]));
}
return objs;
};

/**
* Create a new txin.
*
Expand Down Expand Up @@ -85,7 +67,7 @@ Transaction.prototype.addInput = function (tx, outIndex) {
index: outIndex
},
script: new Script(),
sequence: 4294967295
sequence: this.defaultSequence
}));
}
};
Expand Down Expand Up @@ -138,7 +120,7 @@ Transaction.prototype.serialize = function () {
var scriptBytes = txin.script.buffer;
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length));
buffer = buffer.concat(scriptBytes);
buffer = buffer.concat(convert.numToBytes(parseInt(txin.sequence),4));
buffer = buffer.concat(txin.sequence);
}
buffer = buffer.concat(convert.numToVarInt(this.outs.length));
for (var i = 0; i < this.outs.length; i++) {
Expand Down Expand Up @@ -246,169 +228,6 @@ Transaction.prototype.clone = function ()
return newTx;
};

/**
* Analyze how this transaction affects a wallet.
*
* Returns an object with properties 'impact', 'type' and 'addr'.
*
* 'impact' is an object, see Transaction#calcImpact.
*
* 'type' can be one of the following:
*
* recv:
* This is an incoming transaction, the wallet received money.
* 'addr' contains the first address in the wallet that receives money
* from this transaction.
*
* self:
* This is an internal transaction, money was sent within the wallet.
* 'addr' is undefined.
*
* sent:
* This is an outgoing transaction, money was sent out from the wallet.
* 'addr' contains the first external address, i.e. the recipient.
*
* other:
* This method was unable to detect what the transaction does. Either it
*/
Transaction.prototype.analyze = function (wallet) {
if (!(wallet instanceof Wallet)) return null;

var allFromMe = true,
allToMe = true,
firstRecvHash = null,
firstMeRecvHash = null,
firstSendHash = null;

for (var i = this.outs.length-1; i >= 0; i--) {
var txout = this.outs[i];
var hash = txout.script.simpleOutPubKeyHash();
if (!wallet.hasHash(hash)) {
allToMe = false;
} else {
firstMeRecvHash = hash;
}
firstRecvHash = hash;
}
for (var i = this.ins.length-1; i >= 0; i--) {
var txin = this.ins[i];
firstSendHash = txin.script.simpleInPubKeyHash();
if (!wallet.hasHash(firstSendHash)) {
allFromMe = false;
break;
}
}

var impact = this.calcImpact(wallet);

var analysis = {};

analysis.impact = impact;

if (impact.sign > 0 && impact.value > 0) {
analysis.type = 'recv';
analysis.addr = new Address(firstMeRecvHash);
} else if (allFromMe && allToMe) {
analysis.type = 'self';
} else if (allFromMe) {
analysis.type = 'sent';
// TODO: Right now, firstRecvHash is the first output, which - if the
// transaction was not generated by this library could be the
// change address.
analysis.addr = new Address(firstRecvHash);
} else {
analysis.type = "other";
}

return analysis;
};

/**
* Get a human-readable version of the data returned by Transaction#analyze.
*
* This is merely a convenience function. Clients should consider implementing
* this themselves based on their UI, I18N, etc.
*/
Transaction.prototype.getDescription = function (wallet) {
var analysis = this.analyze(wallet);

if (!analysis) return "";

switch (analysis.type) {
case 'recv':
return "Received with "+analysis.addr;
break;

case 'sent':
return "Payment to "+analysis.addr;
break;

case 'self':
return "Payment to yourself";
break;

case 'other':
default:
return "";
}
};

/**
* Get the total amount of a transaction's outputs.
*/
Transaction.prototype.getTotalOutValue = function () {
return this.outs.reduce(function(t,o) { return t + o.value },0);
};

/**
* Old name for Transaction#getTotalOutValue.
*
* @deprecated
*/
Transaction.prototype.getTotalValue = Transaction.prototype.getTotalOutValue;

/**
* Calculates the impact a transaction has on this wallet.
*
* Based on the its public keys, the wallet will calculate the
* credit or debit of this transaction.
*
* It will return an object with two properties:
* - sign: 1 or -1 depending on sign of the calculated impact.
* - value: amount of calculated impact
*
* @returns Object Impact on wallet
*/
Transaction.prototype.calcImpact = function (wallet) {
if (!(wallet instanceof Wallet)) return 0;

// Calculate credit to us from all outputs
var valueOut = this.outs.filter(function(o) {
return wallet.hasHash(convert.bytesToHex(o.script.simpleOutPubKeyHash()));
})
.reduce(function(t,o) { return t+o.value },0);

var valueIn = this.ins.filter(function(i) {
return wallet.hasHash(convert.bytesToHex(i.script.simpleInPubKeyHash()))
&& wallet.txIndex[i.outpoint.hash];
})
.reduce(function(t,i) {
return t + wallet.txIndex[i.outpoint.hash].outs[i.outpoint.index].value
},0);

if (valueOut > valueIn) {
return {
sign: 1,
value: valueOut - valueIn
};
} else {
return {
sign: -1,
value: valueIn - valueOut
};
}
};

/**
* Converts a serialized transaction into a transaction object
*/
Expand Down Expand Up @@ -451,7 +270,7 @@ Transaction.deserialize = function(buffer) {
index: readAsInt(4)
},
script: new Script(readVarString()),
sequence: readAsInt(4)
sequence: readBytes(4)
});
}
var outs = readVarInt();
Expand All @@ -473,6 +292,9 @@ Transaction.deserialize = function(buffer) {
Transaction.prototype.sign = function(index, key, type) {
type = type || SIGHASH_ALL;
key = new ECKey(key);

// TODO: getPub is slow, sha256ripe160 probably is too.
// This could be sped up a lot by providing these as inputs.
var pub = key.getPub().export('bytes'),
hash160 = util.sha256ripe160(pub),
script = Script.createOutputScript(new Address(hash160)),
Expand Down Expand Up @@ -533,7 +355,6 @@ Transaction.prototype.validateSig = function(index, script, sig, pub) {
convert.coerceToBytes(pub));
}


var TransactionIn = function (data) {
if (typeof data == "string")
this.outpoint = { hash: data.split(':')[0], index: data.split(':')[1] }
Expand All @@ -549,7 +370,7 @@ var TransactionIn = function (data) {
else
this.script = new Script(data.script)

this.sequence = data.sequence || 4294967295;
this.sequence = data.sequence || this.defaultSequence
};

TransactionIn.prototype.clone = function () {
Expand Down
4 changes: 2 additions & 2 deletions test/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('Transaction', function() {
assert.equal(tx.ins.length, 1)

var input = tx.ins[0]
assert.equal(input.sequence, 4294967295)
assert.deepEqual(input.sequence, [255, 255, 255, 255])

assert.equal(input.outpoint.index, 0)
assert.equal(input.outpoint.hash, "69d02fc05c4e0ddc87e796eee42693c244a3112fffe1f762c3fb61ffcb304634")
Expand Down Expand Up @@ -98,7 +98,7 @@ describe('Transaction', function() {
assert.equal(tx.ins.length, 1)

var input = tx.ins[0]
assert.equal(input.sequence, 4294967295)
assert.deepEqual(input.sequence, [255, 255, 255, 255])

assert.equal(input.outpoint.index, 0)
assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57")
Expand Down