Merge pull request #1114 from eordano/docs/transactionSerialization
Add more docs on serialization checks
This commit is contained in:
commit
8bc0ec5567
|
@ -142,13 +142,27 @@ There are a series of methods used for serialization:
|
||||||
* `toObject`: Returns a plain javascript object with no methods and enough information to fully restore the state of this transaction. Using other serialization methods (except for `toJSON`) will cause a some information to be lost.
|
* `toObject`: Returns a plain javascript object with no methods and enough information to fully restore the state of this transaction. Using other serialization methods (except for `toJSON`) will cause a some information to be lost.
|
||||||
* `toJSON`: Returns a string with a JSON-encoded version of the output for `toObject`.
|
* `toJSON`: Returns a string with a JSON-encoded version of the output for `toObject`.
|
||||||
* `toString` or `uncheckedSerialize`: Returns an hexadecimal serialization of the transaction, in the [serialization format for bitcoin](https://bitcoin.org/en/developer-reference#raw-transaction-format).
|
* `toString` or `uncheckedSerialize`: Returns an hexadecimal serialization of the transaction, in the [serialization format for bitcoin](https://bitcoin.org/en/developer-reference#raw-transaction-format).
|
||||||
* `serialize`: Does a series of checks before serializing the transaction:
|
* `serialize`: Does a series of checks before serializing the transaction
|
||||||
- Check that the fee to be used is not very small or very large
|
|
||||||
- Check for dust outputs
|
|
||||||
* `inspect`: Returns a string with some information about the transaction (currently a string formated as `<Transaction 000...000>`, that only shows the serialized value of the transaction.
|
* `inspect`: Returns a string with some information about the transaction (currently a string formated as `<Transaction 000...000>`, that only shows the serialized value of the transaction.
|
||||||
* `toBuffer`: Serializes the transaction for sending over the wire in the bitcoin network
|
* `toBuffer`: Serializes the transaction for sending over the wire in the bitcoin network
|
||||||
* `toBufferWriter`: Uses an already existing BufferWriter to copy over the serialized transaction
|
* `toBufferWriter`: Uses an already existing BufferWriter to copy over the serialized transaction
|
||||||
|
|
||||||
|
## Serialization Checks
|
||||||
|
|
||||||
|
When serializing, the bitcore library performs a series of checks. These can be disabled by providing an object to the `serialize` method with the checks that you'll like to skip.
|
||||||
|
|
||||||
|
* `disableLargeFees` avoids checking that the fee is no more than `Transaction.FEE_PER_KB * Transaction.FEE_SECURITY_MARGIN * size_in_kb`.
|
||||||
|
* `disableSmallFees` avoids checking that the fee is less than `Transaction.FEE_PER_KB * size_in_kb / Transaction.FEE_SECURITY_MARGIN`.
|
||||||
|
* `disableIsFullySigned` does not check if all inputs are fully signed
|
||||||
|
* `disableDustOutputs` does not check for dust outputs being generated
|
||||||
|
* `disableMoreOutputThanInput` avoids checking that the sum of the output amounts is less than or equal to the sum of the amounts for the outputs being spent in the transaction
|
||||||
|
|
||||||
|
These are the current default values in the bitcore library involved on these checks:
|
||||||
|
|
||||||
|
* `Transaction.FEE_PER_KB`: `10000` (satoshis per kilobyte)
|
||||||
|
* `Transaction.FEE_SECURITY_MARGIN`: `15`
|
||||||
|
* `Transaction.DUST_AMOUNT`: `546` (satoshis)
|
||||||
|
|
||||||
## Fee calculation
|
## Fee calculation
|
||||||
|
|
||||||
When outputs' value don't sum up to the same amount that inputs, the difference in bitcoins goes to the miner of the block that includes this transaction. The concept of a "change address" usually is associated with this: an output with an address that can be spent by the creator of the transaction.
|
When outputs' value don't sum up to the same amount that inputs, the difference in bitcoins goes to the miner of the block that includes this transaction. The concept of a "change address" usually is associated with this: an output with an address that can be spent by the creator of the transaction.
|
||||||
|
|
|
@ -24,10 +24,6 @@ var PrivateKey = require('../privatekey');
|
||||||
var Block = require('../block');
|
var Block = require('../block');
|
||||||
var BN = require('../crypto/bn');
|
var BN = require('../crypto/bn');
|
||||||
|
|
||||||
var CURRENT_VERSION = 1;
|
|
||||||
var DEFAULT_NLOCKTIME = 0;
|
|
||||||
var DEFAULT_SEQNUMBER = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a transaction, a set of inputs and outputs to change ownership of tokens
|
* Represents a transaction, a set of inputs and outputs to change ownership of tokens
|
||||||
*
|
*
|
||||||
|
@ -62,6 +58,16 @@ function Transaction(serialized) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var CURRENT_VERSION = 1;
|
||||||
|
var DEFAULT_NLOCKTIME = 0;
|
||||||
|
var DEFAULT_SEQNUMBER = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
// Minimum amount for an output for it not to be considered a dust output
|
||||||
|
Transaction.DUST_AMOUNT = 546;
|
||||||
|
|
||||||
|
// Margin of error to allow fees in the vecinity of the expected value but doesn't allow a big difference
|
||||||
|
Transaction.FEE_SECURITY_MARGIN = 15;
|
||||||
|
|
||||||
// max amount of satoshis in circulation
|
// max amount of satoshis in circulation
|
||||||
Transaction.MAX_MONEY = 21000000 * 1e8;
|
Transaction.MAX_MONEY = 21000000 * 1e8;
|
||||||
|
|
||||||
|
@ -71,6 +77,13 @@ Transaction.NLOCKTIME_BLOCKHEIGHT_LIMIT = 5e8;
|
||||||
// Max value for an unsigned 32 bit value
|
// Max value for an unsigned 32 bit value
|
||||||
Transaction.NLOCKTIME_MAX_VALUE = 4294967295;
|
Transaction.NLOCKTIME_MAX_VALUE = 4294967295;
|
||||||
|
|
||||||
|
// Value used for fee estimation (satoshis per kilobyte)
|
||||||
|
Transaction.FEE_PER_KB = 10000;
|
||||||
|
|
||||||
|
// Safe upper bound for change address script size in bytes
|
||||||
|
Transaction.CHANGE_OUTPUT_MAX_SIZE = 20 + 4 + 34 + 4;
|
||||||
|
Transaction.MAXIMUM_EXTRA_SIZE = 4 + 9 + 9 + 4;
|
||||||
|
|
||||||
/* Constructors and Serialization */
|
/* Constructors and Serialization */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,6 +153,8 @@ Transaction.prototype.uncheckedSerialize = Transaction.prototype.toString = func
|
||||||
Transaction.prototype.checkedSerialize = function(opts) {
|
Transaction.prototype.checkedSerialize = function(opts) {
|
||||||
var serializationError = this.getSerializationError(opts);
|
var serializationError = this.getSerializationError(opts);
|
||||||
if (serializationError) {
|
if (serializationError) {
|
||||||
|
serializationError.message += ' Use Transaction#uncheckedSerialize if you want to skip security checks. ' +
|
||||||
|
'See http://bitcore.io/guide/transaction.html#Serialization for more info.'
|
||||||
throw serializationError;
|
throw serializationError;
|
||||||
}
|
}
|
||||||
return this.uncheckedSerialize();
|
return this.uncheckedSerialize();
|
||||||
|
@ -179,8 +194,6 @@ Transaction.prototype.getSerializationError = function(opts) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.FEE_SECURITY_MARGIN = 15;
|
|
||||||
|
|
||||||
Transaction.prototype._isFeeTooLarge = function() {
|
Transaction.prototype._isFeeTooLarge = function() {
|
||||||
var fee = this._getUnspentValue();
|
var fee = this._getUnspentValue();
|
||||||
var maximumFee = Math.floor(Transaction.FEE_SECURITY_MARGIN * this._estimateFee());
|
var maximumFee = Math.floor(Transaction.FEE_SECURITY_MARGIN * this._estimateFee());
|
||||||
|
@ -201,8 +214,6 @@ Transaction.prototype._missingChange = function() {
|
||||||
return !this._changeScript;
|
return !this._changeScript;
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.DUST_AMOUNT = 5460;
|
|
||||||
|
|
||||||
Transaction.prototype._hasDustOutputs = function() {
|
Transaction.prototype._hasDustOutputs = function() {
|
||||||
var index, output;
|
var index, output;
|
||||||
for (index in this.outputs) {
|
for (index in this.outputs) {
|
||||||
|
@ -712,10 +723,6 @@ Transaction.prototype._clearSignatures = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.FEE_PER_KB = 10000;
|
|
||||||
// Safe upper bound for change address script
|
|
||||||
Transaction.CHANGE_OUTPUT_MAX_SIZE = 20 + 4 + 34 + 4;
|
|
||||||
|
|
||||||
Transaction._estimateFee = function(size, amountAvailable) {
|
Transaction._estimateFee = function(size, amountAvailable) {
|
||||||
var fee = Math.ceil(size / Transaction.FEE_PER_KB);
|
var fee = Math.ceil(size / Transaction.FEE_PER_KB);
|
||||||
if (amountAvailable > fee) {
|
if (amountAvailable > fee) {
|
||||||
|
@ -724,8 +731,6 @@ Transaction._estimateFee = function(size, amountAvailable) {
|
||||||
return Math.ceil(size / 1000) * Transaction.FEE_PER_KB;
|
return Math.ceil(size / 1000) * Transaction.FEE_PER_KB;
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.MAXIMUM_EXTRA_SIZE = 4 + 9 + 9 + 4;
|
|
||||||
|
|
||||||
Transaction.prototype._estimateSize = function() {
|
Transaction.prototype._estimateSize = function() {
|
||||||
var result = Transaction.MAXIMUM_EXTRA_SIZE;
|
var result = Transaction.MAXIMUM_EXTRA_SIZE;
|
||||||
_.each(this.inputs, function(input) {
|
_.each(this.inputs, function(input) {
|
||||||
|
|
|
@ -322,13 +322,23 @@ describe('Transaction', function() {
|
||||||
it('fails if a dust output is created', function() {
|
it('fails if a dust output is created', function() {
|
||||||
var transaction = new Transaction()
|
var transaction = new Transaction()
|
||||||
.from(simpleUtxoWith1BTC)
|
.from(simpleUtxoWith1BTC)
|
||||||
.to(toAddress, 1)
|
.to(toAddress, 545)
|
||||||
.change(changeAddress)
|
.change(changeAddress)
|
||||||
.sign(privateKey);
|
.sign(privateKey);
|
||||||
expect(function() {
|
expect(function() {
|
||||||
return transaction.serialize();
|
return transaction.serialize();
|
||||||
}).to.throw(errors.Transaction.DustOutputs);
|
}).to.throw(errors.Transaction.DustOutputs);
|
||||||
});
|
});
|
||||||
|
it('doesn\'t fail if a dust output is not dust', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.to(toAddress, 546)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
expect(function() {
|
||||||
|
return transaction.serialize();
|
||||||
|
}).to.not.throw(errors.Transaction.DustOutputs);
|
||||||
|
});
|
||||||
it('doesn\'t fail if a dust output is an op_return', function() {
|
it('doesn\'t fail if a dust output is an op_return', function() {
|
||||||
var transaction = new Transaction()
|
var transaction = new Transaction()
|
||||||
.from(simpleUtxoWith1BTC)
|
.from(simpleUtxoWith1BTC)
|
||||||
|
|
Loading…
Reference in New Issue