Skip to content

Commit 8650345

Browse files
fix(lib-cash): match bitcore-lib estimateFee fix
1 parent 96eda6f commit 8650345

File tree

3 files changed

+50
-39
lines changed

3 files changed

+50
-39
lines changed

packages/bitcore-lib-cash/lib/transaction/transaction.js

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ Transaction.NLOCKTIME_MAX_VALUE = 4294967295;
8080
// Value used for fee estimation (satoshis per kilobyte)
8181
Transaction.FEE_PER_KB = 100000;
8282

83-
// Value used for fee estimation (satoshis per byte)
84-
Transaction.FEE_PER_BYTE = 1;
8583

8684
// Safe upper bound for change address script size in bytes
8785
Transaction.CHANGE_OUTPUT_MAX_SIZE = 20 + 4 + 34 + 4;
@@ -235,6 +233,14 @@ Transaction.prototype._hasFeeError = function(opts, unspent) {
235233
);
236234
}
237235
}
236+
if (!opts.disableSmallFees) {
237+
var minimumFee = Math.ceil(this._estimateFee() / Transaction.FEE_SECURITY_MARGIN);
238+
if (unspent < minimumFee) {
239+
return new errors.Transaction.FeeError.TooSmall(
240+
'expected more than ' + minimumFee + ' but got ' + unspent
241+
);
242+
}
243+
}
238244
};
239245

240246
Transaction.prototype._missingChange = function() {
@@ -468,7 +474,7 @@ Transaction.prototype.getLockTime = function() {
468474
};
469475

470476
Transaction.prototype.fromString = function(string) {
471-
this.fromBuffer(new buffer.Buffer(string, 'hex'));
477+
this.fromBuffer(buffer.Buffer.from(string, 'hex'));
472478
};
473479

474480
Transaction.prototype._newTransaction = function() {
@@ -668,7 +674,6 @@ Transaction.prototype.fee = function(amount) {
668674
* Manually set the fee per KB for this transaction. Beware that this resets all the signatures
669675
* for inputs (in further versions, SIGHASH_SINGLE or SIGHASH_NONE signatures will not
670676
* be reset).
671-
* Takes priority over fee per Byte, for backwards compatibility
672677
*
673678
* @param {number} amount satoshis per KB to be sent
674679
* @return {Transaction} this, for chaining
@@ -905,14 +910,19 @@ Transaction.prototype.getFee = function() {
905910
/**
906911
* Estimates fee from serialized transaction size in bytes.
907912
*/
908-
Transaction.prototype._estimateFee = function() {
913+
Transaction.prototype._estimateFee = function () {
909914
var estimatedSize = this._estimateSize();
910915
var available = this._getUnspentValue();
911-
if (this._feePerByte && !this._feePerKb) {
912-
return Transaction._estimateFeePerByte(estimatedSize, available, this._feePerByte);
913-
} else {
914-
return Transaction._estimateFeePerKb(estimatedSize, available, this._feePerKb);
916+
var feeRate = this._feePerByte || (this._feePerKb || Transaction.FEE_PER_KB) / 1000;
917+
function getFee(size) {
918+
return size * feeRate;
919+
}
920+
var fee = Math.ceil(getFee(estimatedSize));
921+
var feeWithChange = Math.ceil(getFee(estimatedSize) + getFee(Transaction.CHANGE_OUTPUT_MAX_SIZE));
922+
if (!this._changeScript || available <= feeWithChange) {
923+
return fee;
915924
}
925+
return feeWithChange;
916926
};
917927

918928
Transaction.prototype._getUnspentValue = function() {
@@ -925,22 +935,6 @@ Transaction.prototype._clearSignatures = function() {
925935
});
926936
};
927937

928-
Transaction._estimateFeePerKb = function(size, amountAvailable, feePerKb) {
929-
var fee = Math.ceil(size / 1000) * (feePerKb || Transaction.FEE_PER_KB);
930-
if (amountAvailable > fee) {
931-
size += Transaction.CHANGE_OUTPUT_MAX_SIZE;
932-
}
933-
return Math.ceil(size / 1000) * (feePerKb || Transaction.FEE_PER_KB);
934-
};
935-
936-
Transaction._estimateFeePerByte = function(size, amountAvailable, feePerByte) {
937-
var fee = size * (feePerByte || Transaction.FEE_PER_BYTE);
938-
if (amountAvailable > fee) {
939-
size += Transaction.CHANGE_OUTPUT_MAX_SIZE;
940-
}
941-
return size * (feePerByte || Transaction.FEE_PER_BYTE);
942-
};
943-
944938
Transaction.prototype._estimateSize = function() {
945939
var result = Transaction.MAXIMUM_EXTRA_SIZE;
946940
_.each(this.inputs, function(input) {

packages/bitcore-lib-cash/test/data/tx_creation.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,6 @@
8080
"change", ["HGR6veMqmYQH8uzKvZFqQBV1xkskQxZEYu"],
8181
"sign", ["L2U9m5My3cdyN5qX1PH4B7XstGDZFWwyukdX8gj8vsJ3fkrqArQo", 1],
8282
"sign", ["L4jFVcDaqZCkknP5KQWjCBgiLFxKxRxywNGTucm3jC3ozByZcbZv", 1],
83-
"serialize", "010000000220c24f763536edb05ce8df2a4816d971be4f20b58451d71589db434aca98bfaf00000000fdfd0000473044022024b955f8bf6aaf0741da011e3214eaec7040cd12694303471cefc6ba0cc4ec290220124738015033a465636dec1524a6f956a229e69d31aef6c7a98b2a291f3cfc6701483045022100e6ae6c43240e8a11a6de2d034501c2a366c0ccdf069c7828de0791f05e68e787022028b80bd36c2b2ae63fe7afb491da6c0ce23fbbb982450962c817b20f0bb24075014c695221020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2210271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b2103a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd853aeffffffffa0644cd1606e081c59eb65fe69d4a83a3a822da423bc392c91712fb77a192edc00000000fc00483045022100ae7f136cf906dc37d34d5035b8d2001c6a783773b74507ba83080e73e903623f0220023baf7738395268f7097e5586130f682b911fd49b83b265f8fa481f2a6b1ee90146304302201d60f512a8b37663d85c123933053e0354f13d89daf699ca600defa03d4a1dab021f41042b6e4ba30311fc3a68c228c3725f3b0f05a4453ef19408e6a4ae30a2b0014c695221020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2210271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b2103a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd853aeffffffff03f04902000000000017a9144de752833233fe69a20064f29b2ca0f6399c8af387007102000000000017a9144de752833233fe69a20064f29b2ca0f6399c8af387ab2f03000000000017a9146c8d8b04c6a1e664b1ec20ec932760760c97688e8700000000"
83+
"serialize", "010000000220c24f763536edb05ce8df2a4816d971be4f20b58451d71589db434aca98bfaf00000000fc004730440220658e8a9a15d05dc72f5ac59823bc82472fa8cdc7853910111dc4717850b4a6ca02201c2e3479704a6581cc8b233fd5533b8f19d093815d60e62b508716ea28010826014730440220054502b9dbf26a962b9b83e719b6c70a55ce6a931d977da0ad26bfcd483d438402205a4ae4193b502592b573bd995d3fa370ef56c6e381ee6356991d14173b0dc396014c695221020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2210271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b2103a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd853aeffffffffa0644cd1606e081c59eb65fe69d4a83a3a822da423bc392c91712fb77a192edc00000000fc0047304402207e1a40f986ee26942f780bb2f384d5425b84079149ee659e3ee8d243568cc02102200519928c0c1cd098d296a64316335db5f894f1ddf27453c4dc07e1cc4f4bbf6d014730440220760ef53cf380eeb6683999090577f9b644bf44039f4a887fdd44ccbd9ece72ba02206ad3cf8bb6c1433a203a818eaa43d89c657dc44f9ac514a02fcc295f3beb405e014c695221020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2210271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b2103a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd853aeffffffff03f04902000000000017a9144de752833233fe69a20064f29b2ca0f6399c8af387007102000000000017a9144de752833233fe69a20064f29b2ca0f6399c8af38723b203000000000017a9146c8d8b04c6a1e664b1ec20ec932760760c97688e8700000000"
8484
]
8585
]

packages/bitcore-lib-cash/test/transaction/transaction.js

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,8 @@ describe('Transaction', function() {
312312
.to(toAddress, 500000)
313313
.change(changeAddress)
314314
.sign(privateKey);
315-
316315
transaction.outputs.length.should.equal(2);
317-
transaction.outputs[1].satoshis.should.equal(400000);
316+
transaction.outputs[1].satoshis.should.equal(477100);
318317
transaction.outputs[1].script.toString()
319318
.should.equal(Script.fromAddress(changeAddress).toString());
320319
var actual = transaction.getChangeOutput().script.toString();
@@ -388,10 +387,10 @@ describe('Transaction', function() {
388387
.sign(privateKey);
389388
transaction._estimateSize().should.be.within(1000, 1999);
390389
transaction.outputs.length.should.equal(2);
391-
transaction.outputs[1].satoshis.should.equal(34000);
390+
transaction.outputs[1].satoshis.should.equal(40464);
392391
});
393-
it('fee per byte (low fee) can be set up manually', function() {
394-
var inputs = _.map(_.range(10), function(i) {
392+
it('fee per byte (low fee) can be set up manually', function () {
393+
var inputs = _.map(_.range(10), function (i) {
395394
var utxo = _.clone(simpleUtxoWith100000Satoshis);
396395
utxo.outputIndex = i;
397396
return utxo;
@@ -406,8 +405,8 @@ describe('Transaction', function() {
406405
transaction.outputs.length.should.equal(2);
407406
transaction.outputs[1].satoshis.should.be.within(48001, 49000);
408407
});
409-
it('fee per byte (high fee) can be set up manually', function() {
410-
var inputs = _.map(_.range(10), function(i) {
408+
it('fee per byte (high fee) can be set up manually', function () {
409+
var inputs = _.map(_.range(10), function (i) {
411410
var utxo = _.clone(simpleUtxoWith100000Satoshis);
412411
utxo.outputIndex = i;
413412
return utxo;
@@ -422,8 +421,8 @@ describe('Transaction', function() {
422421
transaction.outputs.length.should.equal(2);
423422
transaction.outputs[1].satoshis.should.be.within(46002, 48000);
424423
});
425-
it('fee per byte can be set up manually', function() {
426-
var inputs = _.map(_.range(10), function(i) {
424+
it('fee per byte can be set up manually', function () {
425+
var inputs = _.map(_.range(10), function (i) {
427426
var utxo = _.clone(simpleUtxoWith100000Satoshis);
428427
utxo.outputIndex = i;
429428
return utxo;
@@ -438,8 +437,8 @@ describe('Transaction', function() {
438437
transaction.outputs.length.should.equal(2);
439438
transaction.outputs[1].satoshis.should.be.within(24013, 37000);
440439
});
441-
it('fee per byte not enough for change', function() {
442-
var inputs = _.map(_.range(10), function(i) {
440+
it('fee per byte not enough for change', function () {
441+
var inputs = _.map(_.range(10), function (i) {
443442
var utxo = _.clone(simpleUtxoWith100000Satoshis);
444443
utxo.outputIndex = i;
445444
return utxo;
@@ -465,6 +464,16 @@ describe('Transaction', function() {
465464
return transaction.serialize();
466465
}).to.throw(errors.Transaction.InvalidSatoshis);
467466
});
467+
it('if fee is too small, fail serialization', function() {
468+
var transaction = new Transaction({ disableDustOutputs: true })
469+
.from(simpleUtxoWith100000Satoshis)
470+
.to(toAddress, 99999)
471+
.change(changeAddress)
472+
.sign(privateKey);
473+
expect(function() {
474+
return transaction.serialize();
475+
}).to.throw(errors.Transaction.FeeError.TooSmall);
476+
});
468477
it('on second call to sign, change is not recalculated', function() {
469478
var transaction = new Transaction()
470479
.from(simpleUtxoWith100000Satoshis)
@@ -623,6 +632,14 @@ describe('Transaction', function() {
623632
.sign(privateKey);
624633
}, 'disableLargeFees', errors.Transaction.FeeError.TooLarge
625634
));
635+
it('can skip the check for a fee that is too small', buildSkipTest(
636+
function(transaction) {
637+
return transaction
638+
.fee(1)
639+
.change(changeAddress)
640+
.sign(privateKey);
641+
}, 'disableSmallFees', errors.Transaction.FeeError.TooSmall
642+
));
626643
it('can skip the check that prevents dust outputs', buildSkipTest(
627644
function(transaction) {
628645
return transaction
@@ -987,7 +1004,7 @@ describe('Transaction', function() {
9871004
.change(changeAddress)
9881005
.to(toAddress, 1000);
9891006
transaction.inputAmount.should.equal(100000000);
990-
transaction.outputAmount.should.equal(99900000);
1007+
transaction.outputAmount.should.equal(99977100);
9911008
});
9921009
it('returns correct values for coinjoin transaction', function() {
9931010
// see livenet tx c16467eea05f1f30d50ed6dbc06a38539d9bb15110e4b7dc6653046a3678a718
@@ -1085,7 +1102,7 @@ describe('Transaction', function() {
10851102
tx.outputs.length.should.equal(2);
10861103
tx.outputs[0].satoshis.should.equal(10000000);
10871104
tx.outputs[0].script.toAddress().toString().should.equal(toAddress);
1088-
tx.outputs[1].satoshis.should.equal(89900000);
1105+
tx.outputs[1].satoshis.should.equal(89977100);
10891106
tx.outputs[1].script.toAddress().toString().should.equal(changeAddress);
10901107
});
10911108

0 commit comments

Comments
 (0)