tx creation working. more tests needed
This commit is contained in:
parent
ada92746b7
commit
671d372c19
16
Address.js
16
Address.js
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
var imports = require('soop').imports();
|
||||
var parent = imports.parent || require('./util/VersionedData');
|
||||
var networks= imports.networks || require('./networks');
|
||||
|
||||
function Address() {
|
||||
Address.super(this, arguments);
|
||||
|
@ -22,4 +23,19 @@ Address.prototype.isValid = function() {
|
|||
return answer;
|
||||
};
|
||||
|
||||
Address.prototype.network = function() {
|
||||
var version = this.version();
|
||||
|
||||
var livenet = networks.livenet;
|
||||
var testnet = networks.testnet;
|
||||
|
||||
var answer;
|
||||
if (version === livenet.addressPubkey || version === livenet.addressScript)
|
||||
answer = livenet;
|
||||
else if (version === testnet.addressPubkey || version === testnet.addressScript)
|
||||
answer = testnet;
|
||||
|
||||
return answer;
|
||||
};
|
||||
|
||||
module.exports = require('soop')(Address);
|
||||
|
|
175
Transaction.js
175
Transaction.js
|
@ -11,8 +11,10 @@ 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 COINBASE_OP = Buffer.concat([util.NULL_HASH, new Buffer('FFFFFFFF', 'hex')]);
|
||||
var DEFAULT_FEE = 0.0001;
|
||||
|
||||
function TransactionIn(data) {
|
||||
if ("object" !== typeof data) {
|
||||
|
@ -605,36 +607,7 @@ Transaction.prototype.fromObj = function fromObj(obj) {
|
|||
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);
|
||||
});
|
||||
var tx = new Transaction(txobj);
|
||||
|
||||
this.lock_time = txobj.lock_time;
|
||||
this.version = txobj.version;
|
||||
|
@ -694,7 +667,8 @@ Transaction.prototype.parse = function (parser) {
|
|||
* }, [...]
|
||||
* ]
|
||||
* This is compatible con insight's /utxo API.
|
||||
* NOTE that amount is in BTCs! (as returned in insight and bitcoind.
|
||||
* NOTE that amount is in BTCs! (as returned in insight and bitcoind)
|
||||
* amountSat can be given to provide amount in satochis.
|
||||
*
|
||||
* @totalNeededAmount: output transaction amount in BTC, including fee
|
||||
*
|
||||
|
@ -705,17 +679,27 @@ Transaction.prototype.parse = function (parser) {
|
|||
*/
|
||||
Transaction.selectUnspent = function (unspentArray, totalNeededAmount) {
|
||||
|
||||
// TODO implement bidcoind heristics
|
||||
// A-
|
||||
// 1) select utxos with 6+ confirmations
|
||||
// 2) if not 2) select utxos with 1+ confirmations
|
||||
// 3) if not select unconfirmed.
|
||||
//
|
||||
// B-
|
||||
// Select smaller utxos first.
|
||||
//
|
||||
//
|
||||
// TODO we could randomize or select the selection
|
||||
|
||||
var selected = [];
|
||||
var l = unspentArray.length;
|
||||
var totalSat = bignum(0);
|
||||
var totalNeededAmountSat = bignum(totalNeededAmount * util.COIN);
|
||||
var totalNeededAmountSat = util.parseValue(totalNeededAmount);
|
||||
var fullfill = false;
|
||||
|
||||
for(var i = 0; i<l; i++) {
|
||||
var u = unspentArray[i];
|
||||
var sat = bignum(u.amount * util.COIN);
|
||||
var sat = u.amountSat || util.parseValue(u.amount);
|
||||
totalSat = totalSat.add(sat);
|
||||
selected.push(u);
|
||||
if(totalSat.cmp(totalNeededAmountSat) >= 0) {
|
||||
|
@ -727,7 +711,132 @@ Transaction.selectUnspent = function (unspentArray, totalNeededAmount) {
|
|||
return selected;
|
||||
}
|
||||
|
||||
/*
|
||||
* _scriptForAddress
|
||||
*
|
||||
* Returns a scriptPubKey for the given address type
|
||||
*/
|
||||
|
||||
Transaction._scriptForAddress = function (addressString) {
|
||||
|
||||
var livenet = networks.livenet;
|
||||
var testnet = networks.testnet;
|
||||
var address = new Address(addressString);
|
||||
|
||||
var version = address.version();
|
||||
var script;
|
||||
if (version == livenet.addressPubkey || version == testnet.addressPubkey)
|
||||
script = Script.createPubKeyHashOut(address.payload());
|
||||
else if (version == livenet.addressScript || version == testnet.addressScript)
|
||||
script = Script.createP2SH(address.payload());
|
||||
else
|
||||
throw new Error('invalid output address');
|
||||
|
||||
return script;
|
||||
};
|
||||
|
||||
/*
|
||||
* create
|
||||
*
|
||||
* creates a transaction
|
||||
*
|
||||
* @ins
|
||||
* a selected set of utxos, as described on selectUnspent
|
||||
* @outs
|
||||
* an array of [{
|
||||
* address: xx,
|
||||
* amount:0.001
|
||||
* },...]
|
||||
* @opts
|
||||
* {
|
||||
* remainderAddress: null,
|
||||
* fee: 0.001,
|
||||
* lockTime: null,
|
||||
* }
|
||||
*
|
||||
* Amounts are in BTC. instead of fee and amount; feeSat and amountSat can be given,
|
||||
* repectively, to provide amounts in satoshis.
|
||||
*
|
||||
* (TODO: should we use uBTC already?)
|
||||
*
|
||||
* If not remainderAddress is given, and there is a remainderAddress
|
||||
* first in address will be used. (TODO: is this is reasoable?)
|
||||
*
|
||||
* TODO add exceptions for validations:
|
||||
* out address - ins amount > out amount - fees < maxFees
|
||||
* + more?
|
||||
*/
|
||||
|
||||
Transaction.create = function (ins, outs, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
var feeSat = opts.feeSat || util.parseValue(opts.fee || DEFAULT_FEE);
|
||||
var txobj = {};
|
||||
txobj.version = 1;
|
||||
txobj.lock_time = opts.lockTime || 0;
|
||||
txobj.ins = [];
|
||||
txobj.outs = [];
|
||||
|
||||
var l = ins.length;
|
||||
var valueInSat = bignum(0);
|
||||
for(var i=0; i<l; i++) {
|
||||
valueInSat = valueInSat.add(util.parseValue(ins[i].amount));
|
||||
|
||||
var txin = {};
|
||||
txin.s = util.EMPTY_BUFFER;
|
||||
txin.q = 0xffffffff;
|
||||
|
||||
var hash = new Buffer(ins[i].txid, 'hex');
|
||||
var hashReversed = buffertools.reverse(hash);
|
||||
|
||||
var vout = parseInt(ins[i].vout);
|
||||
var voutBuf = new Buffer(4);
|
||||
voutBuf.writeUInt32LE(vout, 0);
|
||||
|
||||
txin.o = Buffer.concat([hashReversed, voutBuf]);
|
||||
txobj.ins.push(txin);
|
||||
}
|
||||
|
||||
|
||||
var valueOutSat = bignum(0);
|
||||
l = outs.length;
|
||||
|
||||
for(var i=0;i<outs.length;i++) {
|
||||
var sat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||
valueOutSat = valueOutSat.add(sat);
|
||||
}
|
||||
|
||||
if (valueInSat.cmp(valueOutSat)<0) {
|
||||
throw new Error('transaction inputs sum less than outputs');
|
||||
}
|
||||
|
||||
var remainderSat = valueInSat.sub(valueOutSat);
|
||||
|
||||
if (remainderSat.cmp(0)>0) {
|
||||
var remainderAddress = opts.remainderAddress || ins[0].address;
|
||||
|
||||
outs.push({
|
||||
address: remainderAddress,
|
||||
amountSat: remainderSat,
|
||||
});
|
||||
}
|
||||
|
||||
for(var i=0;i<outs.length;i++) {
|
||||
var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||
var value = util.bigIntToValue(amountSat);
|
||||
var script = Transaction._scriptForAddress(outs[i].address);
|
||||
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
|
||||
txobj.outs.push(txout);
|
||||
}
|
||||
|
||||
|
||||
return new Transaction(txobj);
|
||||
};
|
||||
|
||||
var TransactionInputsCache = exports.TransactionInputsCache =
|
||||
function TransactionInputsCache(tx)
|
||||
|
|
|
@ -10,6 +10,7 @@ var Transaction;
|
|||
var In;
|
||||
var Out;
|
||||
var Script = bitcore.Script;
|
||||
var util = bitcore.util;
|
||||
var buffertools = require('buffertools');
|
||||
var testdata = testdata || require('./testdata');
|
||||
|
||||
|
@ -33,7 +34,7 @@ describe('Transaction', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should be able to select unspents', function() {
|
||||
it('should be able to select utxos', function() {
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspends,1.0);
|
||||
u.length.should.equal(3);
|
||||
u = Transaction.selectUnspent(testdata.dataUnspends,0.5);
|
||||
|
@ -52,11 +53,24 @@ describe('Transaction', function() {
|
|||
should.exist(u[0].vout);
|
||||
});
|
||||
|
||||
|
||||
it.skip('should be able to create instance thru #create', function() {
|
||||
var t = Transaction.create({
|
||||
it('should return null if not enough utxos', function() {
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspends,1.12);
|
||||
u.length.should.equal(0);
|
||||
});
|
||||
should.exist(t);
|
||||
|
||||
|
||||
it('should be able to create instance thru #create', function() {
|
||||
var utxos = Transaction.selectUnspent(testdata.dataUnspends,0.1);
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
var tx = Transaction.create(utxos, outs,
|
||||
{remainderAddress:'3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou'});
|
||||
should.exist(tx);
|
||||
tx.version.should.equal(1);
|
||||
tx.ins.length.should.equal(2);
|
||||
tx.outs.length.should.equal(2);
|
||||
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
|
||||
// TODO remainder is 0.03 here because unspend just select utxos in order
|
||||
util.valueToBigInt(tx.outs[1].v).cmp(3000000).should.equal(0);
|
||||
});
|
||||
|
||||
// Read tests from test/data/tx_valid.json
|
||||
|
|
|
@ -188,6 +188,9 @@ function parseWholeValue(res)
|
|||
|
||||
exports.parseValue = function parseValue(valueStr)
|
||||
{
|
||||
if (typeof valueStr !== 'string')
|
||||
valueStr = valueStr.toString();
|
||||
|
||||
var res = valueStr.match(reFullVal);
|
||||
if (res)
|
||||
return parseFullValue(res);
|
||||
|
|
Loading…
Reference in New Issue