Browse Source

dynamic fee

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

83
Transaction.js

@ -16,7 +16,7 @@ var WalletKey = imports.WalletKey || require('./WalletKey');
var PrivateKey = imports.PrivateKey || require('./PrivateKey'); var PrivateKey = imports.PrivateKey || require('./PrivateKey');
var COINBASE_OP = Buffer.concat([util.NULL_HASH, new Buffer('FFFFFFFF', 'hex')]); 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) { function TransactionIn(data) {
if ("object" !== typeof data) { if ("object" !== typeof data) {
@ -777,7 +777,7 @@ Transaction._sumOutputs = function(outs) {
Transaction.prepare = function (ins, outs, opts) { Transaction.prepare = function (ins, outs, opts) {
opts = 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 = {}; var txobj = {};
txobj.version = 1; txobj.version = 1;
txobj.lock_time = opts.lockTime || 0; txobj.lock_time = opts.lockTime || 0;
@ -815,27 +815,27 @@ Transaction.prepare = function (ins, outs, opts) {
inv + ' < '+ouv + ' [SAT]'); 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++) { for(var i=0;i<outs.length;i++) {
var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount); var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount);
var value = util.bigIntToValue(amountSat); var value = util.bigIntToValue(amountSat);
var script = Transaction._scriptForAddress(outs[i].address); var script = Transaction._scriptForAddress(outs[i].address);
var txout = { var txout = {
v: value, v: value,
s: script.getBuffer(), 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); txobj.outs.push(txout);
} }
@ -843,6 +843,31 @@ Transaction.prepare = function (ins, outs, opts) {
return new Transaction(txobj); 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 () { Transaction.prototype.isComplete = function () {
var l = this.ins.length; var l = this.ins.length;
@ -1021,9 +1046,24 @@ Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
Transaction.create = function (utxos, outs, keys, opts) { Transaction.create = function (utxos, outs, keys, opts) {
//starting size estimation
var size = 500;
var opts = opts || {};
var givenFeeSat;
if (opts.fee || opts.feeSat) {
givenFeeSat = opts.fee ? opts.fee * util.COIN : opts.feeSat;
}
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 var valueOutSat = Transaction
._sumOutputs(outs) ._sumOutputs(outs)
.add(DEFAULT_FEE * util.COIN); .add(feeSat);
var selectedUtxos = Transaction var selectedUtxos = Transaction
.selectUnspent(utxos,valueOutSat / util.COIN, opts.allowUnconfirmed); .selectUnspent(utxos,valueOutSat / util.COIN, opts.allowUnconfirmed);
@ -1032,12 +1072,19 @@ Transaction.create = function (utxos, outs, keys, opts) {
throw new Error( throw new Error(
'the given UTXOs dont sum up the given outputs: ' 'the given UTXOs dont sum up the given outputs: '
+ valueOutSat.toString() + valueOutSat.toString()
+ ' SAT' + ' (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 );
var tx = Transaction.prepare(selectedUtxos, outs, opts);
//TODO interate with the new TX fee
if (keys) tx.sign(selectedUtxos, keys); if (keys) tx.sign(selectedUtxos, keys);
return tx; return tx;
}; };

2
test/data/unspentSign.json

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

62
test/test.Transaction.js

@ -94,8 +94,9 @@ describe('Transaction', function() {
tx.outs.length.should.equal(2); tx.outs.length.should.equal(2);
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0); util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
// remainder is 0.0299 here because unspent select utxos in order // 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); tx.isComplete().should.equal(false);
}); });
@ -106,6 +107,12 @@ describe('Transaction', function() {
.create .create
.bind(utxos, outs, null, opts) .bind(utxos, outs, null, opts)
.should.throw(); .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}' // 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 // no remainder
outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}]; outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
@ -132,10 +139,14 @@ describe('Transaction', function() {
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}]; var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
var tx = Transaction.create(utxos, outs, testdata.dataUnspentSign.keyStrings, opts); var tx = Transaction.create(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
tx.isComplete().should.equal(true); tx.isComplete().should.equal(true);
tx.ins.length.should.equal(1);
tx.outs.length.should.equal(2);
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}]; var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}];
var tx2 = Transaction.create(utxos, outs2, testdata.dataUnspentSign.keyStrings, opts); var tx2 = Transaction.create(utxos, outs2, testdata.dataUnspentSign.keyStrings, opts);
tx2.isComplete().should.equal(true); 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() { it('#sign should sign an incomplete tx ', function() {
@ -143,6 +154,8 @@ describe('Transaction', function() {
var utxos =testdata.dataUnspentSign.unspent; var utxos =testdata.dataUnspentSign.unspent;
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}]; var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
var tx = Transaction.create(utxos, outs, keys, opts); 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); tx.isComplete().should.equal(false);
}); });
it('#sign should sign a tx in multiple steps', function() { 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 // Read tests from test/data/tx_valid.json
// Format is an array of arrays // Format is an array of arrays
// Inner arrays are either [ "comment" ] // Inner arrays are either [ "comment" ]

Loading…
Cancel
Save