Many cleanups to Transaction, see detailed.
Default-ize the sequence rather than use a number, and default to bytes for input. I doubt anybody ever uses this anyways. Remove weird convenience code, and remove wallet logic. Checking a TX's affects on a wallet should be managed by the wallet object. Remove parsing for the weirder SIGHASH types. People use this library for creating SIGHASH_ALL transactions, and I don't see the need to support these other types at the moment since this library's more used for wallets than for hardcore bitcoin tx analysis/creation. They weren't tested anyways. Add note about potentially improving performance by providing pubkey/address. Deriving from the private key is slower, that information should probably be cached by the end user.
This commit is contained in:
parent
b94f9a687f
commit
28e146431c
2 changed files with 10 additions and 206 deletions
|
@ -2,7 +2,6 @@ var BigInteger = require('./jsbn/jsbn');
|
||||||
var Script = require('./script');
|
var Script = require('./script');
|
||||||
var util = require('./util');
|
var util = require('./util');
|
||||||
var convert = require('./convert');
|
var convert = require('./convert');
|
||||||
var Wallet = require('./wallet');
|
|
||||||
var ECKey = require('./eckey').ECKey;
|
var ECKey = require('./eckey').ECKey;
|
||||||
var ECDSA = require('./ecdsa');
|
var ECDSA = require('./ecdsa');
|
||||||
var Address = require('./address');
|
var Address = require('./address');
|
||||||
|
@ -15,8 +14,7 @@ var Transaction = function (doc) {
|
||||||
this.locktime = 0;
|
this.locktime = 0;
|
||||||
this.ins = [];
|
this.ins = [];
|
||||||
this.outs = [];
|
this.outs = [];
|
||||||
this.timestamp = null;
|
this.defaultSequence = [255, 255, 255, 255] // 0xFFFFFFFF
|
||||||
this.block = null;
|
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
if (typeof doc == "string" || Array.isArray(doc)) {
|
if (typeof doc == "string" || Array.isArray(doc)) {
|
||||||
|
@ -35,27 +33,11 @@ var Transaction = function (doc) {
|
||||||
this.addOutput(new TransactionOut(doc.outs[i]));
|
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()
|
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.
|
* Create a new txin.
|
||||||
*
|
*
|
||||||
|
@ -85,7 +67,7 @@ Transaction.prototype.addInput = function (tx, outIndex) {
|
||||||
index: outIndex
|
index: outIndex
|
||||||
},
|
},
|
||||||
script: new Script(),
|
script: new Script(),
|
||||||
sequence: 4294967295
|
sequence: this.defaultSequence
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -138,7 +120,7 @@ Transaction.prototype.serialize = function () {
|
||||||
var scriptBytes = txin.script.buffer;
|
var scriptBytes = txin.script.buffer;
|
||||||
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length));
|
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length));
|
||||||
buffer = buffer.concat(scriptBytes);
|
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));
|
buffer = buffer.concat(convert.numToVarInt(this.outs.length));
|
||||||
for (var i = 0; i < this.outs.length; i++) {
|
for (var i = 0; i < this.outs.length; i++) {
|
||||||
|
@ -191,23 +173,6 @@ function (connectedScript, inIndex, hashType)
|
||||||
|
|
||||||
txTmp.ins[inIndex].script = connectedScript;
|
txTmp.ins[inIndex].script = connectedScript;
|
||||||
|
|
||||||
// Blank out some of the outputs
|
|
||||||
if ((hashType & 0x1f) == SIGHASH_NONE) {
|
|
||||||
txTmp.outs = [];
|
|
||||||
|
|
||||||
// Let the others update at will
|
|
||||||
for (var i = 0; i < txTmp.ins.length; i++)
|
|
||||||
if (i != inIndex)
|
|
||||||
txTmp.ins[i].sequence = 0;
|
|
||||||
} else if ((hashType & 0x1f) == SIGHASH_SINGLE) {
|
|
||||||
// TODO: Implement
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blank out other inputs completely, not recommended for open transactions
|
|
||||||
if (hashType & SIGHASH_ANYONECANPAY) {
|
|
||||||
txTmp.ins = [txTmp.ins[inIndex]];
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = txTmp.serialize();
|
var buffer = txTmp.serialize();
|
||||||
|
|
||||||
buffer = buffer.concat(convert.numToBytes(parseInt(hashType),4));
|
buffer = buffer.concat(convert.numToBytes(parseInt(hashType),4));
|
||||||
|
@ -246,169 +211,6 @@ Transaction.prototype.clone = function ()
|
||||||
return newTx;
|
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
|
* Converts a serialized transaction into a transaction object
|
||||||
*/
|
*/
|
||||||
|
@ -451,7 +253,7 @@ Transaction.deserialize = function(buffer) {
|
||||||
index: readAsInt(4)
|
index: readAsInt(4)
|
||||||
},
|
},
|
||||||
script: new Script(readVarString()),
|
script: new Script(readVarString()),
|
||||||
sequence: readAsInt(4)
|
sequence: readBytes(4)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var outs = readVarInt();
|
var outs = readVarInt();
|
||||||
|
@ -473,6 +275,9 @@ Transaction.deserialize = function(buffer) {
|
||||||
Transaction.prototype.sign = function(index, key, type) {
|
Transaction.prototype.sign = function(index, key, type) {
|
||||||
type = type || SIGHASH_ALL;
|
type = type || SIGHASH_ALL;
|
||||||
key = new ECKey(key);
|
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'),
|
var pub = key.getPub().export('bytes'),
|
||||||
hash160 = util.sha256ripe160(pub),
|
hash160 = util.sha256ripe160(pub),
|
||||||
script = Script.createOutputScript(new Address(hash160)),
|
script = Script.createOutputScript(new Address(hash160)),
|
||||||
|
@ -533,7 +338,6 @@ Transaction.prototype.validateSig = function(index, script, sig, pub) {
|
||||||
convert.coerceToBytes(pub));
|
convert.coerceToBytes(pub));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var TransactionIn = function (data) {
|
var TransactionIn = function (data) {
|
||||||
if (typeof data == "string")
|
if (typeof data == "string")
|
||||||
this.outpoint = { hash: data.split(':')[0], index: data.split(':')[1] }
|
this.outpoint = { hash: data.split(':')[0], index: data.split(':')[1] }
|
||||||
|
@ -549,7 +353,7 @@ var TransactionIn = function (data) {
|
||||||
else
|
else
|
||||||
this.script = new Script(data.script)
|
this.script = new Script(data.script)
|
||||||
|
|
||||||
this.sequence = data.sequence || 4294967295;
|
this.sequence = data.sequence || this.defaultSequence
|
||||||
};
|
};
|
||||||
|
|
||||||
TransactionIn.prototype.clone = function () {
|
TransactionIn.prototype.clone = function () {
|
||||||
|
|
|
@ -36,7 +36,7 @@ describe('Transaction', function() {
|
||||||
assert.equal(tx.ins.length, 1)
|
assert.equal(tx.ins.length, 1)
|
||||||
|
|
||||||
var input = tx.ins[0]
|
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.index, 0)
|
||||||
assert.equal(input.outpoint.hash, "69d02fc05c4e0ddc87e796eee42693c244a3112fffe1f762c3fb61ffcb304634")
|
assert.equal(input.outpoint.hash, "69d02fc05c4e0ddc87e796eee42693c244a3112fffe1f762c3fb61ffcb304634")
|
||||||
|
@ -98,7 +98,7 @@ describe('Transaction', function() {
|
||||||
assert.equal(tx.ins.length, 1)
|
assert.equal(tx.ins.length, 1)
|
||||||
|
|
||||||
var input = tx.ins[0]
|
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.index, 0)
|
||||||
assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57")
|
assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57")
|
||||||
|
|
Loading…
Add table
Reference in a new issue