dynamic fee
This commit is contained in:
parent
0c83ecf5fb
commit
041f06aae1
103
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;
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
"scriptPubKey": "76a914fe021bac469a5c49915b2a8ffa7390a9ce5580f988ac",
|
||||
"vout": 1,
|
||||
"amount": 1.01,
|
||||
"amount": 1.0101,
|
||||
"confirmations":7
|
||||
},
|
||||
{
|
||||
|
||||
@ -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…
Reference in New Issue
Block a user