Browse Source

dynamic fee

patch-2
Matias Alejo Garcia 11 years ago
parent
commit
041f06aae1
  1. 103
      Transaction.js
  2. 2
      test/data/unspentSign.json
  3. 62
      test/test.Transaction.js

103
Transaction.js

@ -16,7 +16,7 @@ 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 DEFAULT_FEE = 0.001;
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
function TransactionIn(data) {
if ("object" !== typeof data) {
@ -777,7 +777,7 @@ Transaction._sumOutputs = function(outs) {
Transaction.prepare = function (ins, outs, opts) {
opts = opts || {};
var feeSat = opts.feeSat || util.parseValue(opts.fee || DEFAULT_FEE);
var feeSat = opts.feeSat || (opts.fee? util.parseValue(opts.fee) : FEE_PER_1000B_SAT );
var txobj = {};
txobj.version = 1;
txobj.lock_time = opts.lockTime || 0;
@ -815,27 +815,27 @@ Transaction.prepare = function (ins, outs, opts) {
inv + ' < '+ouv + ' [SAT]');
}
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);
}
// add remainder (without modifiying outs[])
var remainderSat = valueInSat.sub(valueOutSat);
if (remainderSat.cmp(0)>0) {
var remainderAddress = opts.remainderAddress || ins[0].address;
var value = util.bigIntToValue(remainderSat);
var script = Transaction._scriptForAddress(remainderAddress);
var txout = {
v: value,
s: script.getBuffer(),
};
txobj.outs.push(txout);
}
@ -843,6 +843,31 @@ Transaction.prepare = function (ins, outs, opts) {
return new Transaction(txobj);
};
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 getHash() {
if (!this.size) {
this.size = this.calcSize();
}
return this.size;
};
Transaction.prototype.isComplete = function () {
var l = this.ins.length;
@ -1021,23 +1046,45 @@ Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
Transaction.create = function (utxos, outs, keys, opts) {
var valueOutSat = Transaction
._sumOutputs(outs)
.add(DEFAULT_FEE * util.COIN);
//starting size estimation
var size = 500;
var opts = opts || {};
var selectedUtxos = Transaction
.selectUnspent(utxos,valueOutSat / util.COIN, opts.allowUnconfirmed);
if (!selectedUtxos) {
throw new Error(
'the given UTXOs dont sum up the given outputs: '
+ valueOutSat.toString()
+ ' SAT'
);
var givenFeeSat;
if (opts.fee || opts.feeSat) {
givenFeeSat = opts.fee ? opts.fee * util.COIN : opts.feeSat;
}
var tx = Transaction.prepare(selectedUtxos, outs, opts);
//TODO interate with the new TX fee
do {
// based on https://en.bitcoin.it/wiki/Transaction_fees
maxSizeK = parseInt(size/1000) + 1;
var feeSat = givenFeeSat
? givenFeeSat : maxSizeK * FEE_PER_1000B_SAT ;
var valueOutSat = Transaction
._sumOutputs(outs)
.add(feeSat);
var selectedUtxos = Transaction
.selectUnspent(utxos,valueOutSat / util.COIN, opts.allowUnconfirmed);
if (!selectedUtxos) {
throw new Error(
'the given UTXOs dont sum up the given outputs: '
+ valueOutSat.toString()
+ ' (fee is ' + feeSat
+ ' )SAT'
);
}
var tx = Transaction.prepare(selectedUtxos, outs, {
feeSat: feeSat,
remainderAddress: opts.remainderAddress,
lockTime: opts.lockTime,
});
size = tx.getSize();
} while (size > (maxSizeK+1)*1000 );
if (keys) tx.sign(selectedUtxos, keys);
return tx;
};

2
test/data/unspentSign.json

@ -5,7 +5,7 @@
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
"scriptPubKey": "76a914fe021bac469a5c49915b2a8ffa7390a9ce5580f988ac",
"vout": 1,
"amount": 1.01,
"amount": 1.0101,
"confirmations":7
},
{

62
test/test.Transaction.js

@ -94,8 +94,9 @@ describe('Transaction', function() {
tx.outs.length.should.equal(2);
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
// remainder is 0.0299 here because unspent select utxos in order
util.valueToBigInt(tx.outs[1].v).cmp(2900000).should.equal(0);
util.valueToBigInt(tx.outs[1].v).cmp(2990000).should.equal(0);
tx.isComplete().should.equal(false);
});
@ -106,6 +107,12 @@ describe('Transaction', function() {
.create
.bind(utxos, outs, null, opts)
.should.throw();
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.5}];
should.exist( Transaction.create(utxos, outs2, null, opts));
// do not allow unconfirmed
Transaction.create.bind(utxos, outs2).should.throw();
});
@ -116,7 +123,7 @@ describe('Transaction', function() {
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08,"mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd":0.0299}'
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0200127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac20402c00000000001976a914b00127584485a7cff0949ef0f6bc5575f06ce00d88ac00000000');
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0200127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388acb09f2d00000000001976a914b00127584485a7cff0949ef0f6bc5575f06ce00d88ac00000000');
// no remainder
outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
@ -132,10 +139,14 @@ describe('Transaction', function() {
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
var tx = Transaction.create(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
tx.isComplete().should.equal(true);
tx.ins.length.should.equal(1);
tx.outs.length.should.equal(2);
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}];
var tx2 = Transaction.create(utxos, outs2, testdata.dataUnspentSign.keyStrings, opts);
tx2.isComplete().should.equal(true);
tx2.ins.length.should.equal(3);
tx2.outs.length.should.equal(2);
});
it('#sign should sign an incomplete tx ', function() {
@ -143,6 +154,8 @@ describe('Transaction', function() {
var utxos =testdata.dataUnspentSign.unspent;
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
var tx = Transaction.create(utxos, outs, keys, opts);
tx.ins.length.should.equal(1);
tx.outs.length.should.equal(2);
tx.isComplete().should.equal(false);
});
it('#sign should sign a tx in multiple steps', function() {
@ -165,6 +178,51 @@ describe('Transaction', function() {
});
it('#create: should generate dynamic fee and readjust (and not) the selected UTXOs', function() {
//this cases exceeds the input by 1mbtc AFTEr calculating the dynamic fee,
//so, it should trigger adding a new 10BTC utxo
var utxos =testdata.dataUnspentSign.unspent;
var outs = [];
var n =101;
for (var i=0; i<n; i++) {
outs.push({address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.01});
}
var tx = Transaction.create(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
tx.getSize().should.equal(3560);
// ins = 11.0101 BTC (2 inputs: 1.0101 + 10 );
tx.ins.length.should.equal(2);
// outs = 101 outs:
// 101 * 0.01 = 1.01BTC; + 0.0004 fee = 1.0104btc
// remainder = 11.0101-1.0104 = 9.9997
tx.outs.length.should.equal(102);
util.valueToBigInt(tx.outs[n].v).cmp(999970000).should.equal(0);
tx.isComplete().should.equal(true);
//this is the complementary case, it does not trigger a new utxo
var utxos =testdata.dataUnspentSign.unspent;
var outs = [];
var n =100;
for (var i=0; i<n; i++) {
outs.push({address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.01});
}
var tx = Transaction.create(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
tx.getSize().should.equal(3485);
// ins = 1.0101 BTC (1 inputs: 1.0101);
tx.ins.length.should.equal(1);
// outs = 100 outs:
// 100 * 0.01 = 1BTC; + 0.0004 fee = 1.0004btc
// remainder = 1.0101-1.0004 = 0.0097
tx.outs.length.should.equal(101);
util.valueToBigInt(tx.outs[n].v).cmp(970000).should.equal(0);
tx.isComplete().should.equal(true);
});
// Read tests from test/data/tx_valid.json
// Format is an array of arrays
// Inner arrays are either [ "comment" ]

Loading…
Cancel
Save