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 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,23 +1046,45 @@ Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
|
|||||||
|
|
||||||
Transaction.create = function (utxos, outs, keys, opts) {
|
Transaction.create = function (utxos, outs, keys, opts) {
|
||||||
|
|
||||||
var valueOutSat = Transaction
|
//starting size estimation
|
||||||
._sumOutputs(outs)
|
var size = 500;
|
||||||
.add(DEFAULT_FEE * util.COIN);
|
var opts = opts || {};
|
||||||
|
|
||||||
var selectedUtxos = Transaction
|
var givenFeeSat;
|
||||||
.selectUnspent(utxos,valueOutSat / util.COIN, opts.allowUnconfirmed);
|
if (opts.fee || opts.feeSat) {
|
||||||
|
givenFeeSat = opts.fee ? opts.fee * util.COIN : opts.feeSat;
|
||||||
if (!selectedUtxos) {
|
|
||||||
throw new Error(
|
|
||||||
'the given UTXOs dont sum up the given outputs: '
|
|
||||||
+ valueOutSat.toString()
|
|
||||||
+ ' SAT'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tx = Transaction.prepare(selectedUtxos, outs, opts);
|
do {
|
||||||
//TODO interate with the new TX fee
|
// 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);
|
if (keys) tx.sign(selectedUtxos, keys);
|
||||||
return tx;
|
return tx;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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…
Reference in New Issue
Block a user