diff --git a/lib/errors/spec.js b/lib/errors/spec.js index c2dd28a..eece532 100644 --- a/lib/errors/spec.js +++ b/lib/errors/spec.js @@ -72,6 +72,15 @@ module.exports = [{ }, { name: 'ChangeAddressMissing', message: 'Change address is missing' + }, { + name: 'BlockHeightTooHigh', + message: 'Block Height can be at most 2^32 -1' + }, { + name: 'NLockTimeOutOfRange', + message: 'Block Height can only be between 0 and 499 999 999' + }, { + name: 'LockTimeTooEarly', + message: 'Lock Time can\'t be earlier than UNIX date 500 000 000' }] }, { name: 'Script', diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 74e06fb..ef28e0a 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -66,6 +66,12 @@ function Transaction(serialized) { // max amount of satoshis in circulation Transaction.MAX_MONEY = 21000000 * 1e8; +// nlocktime limit to be considered block height rather than a timestamp +Transaction.NLOCKTIME_BLOCKHEIGHT_LIMIT = 5e8; + +// Max value for an unsigned 32 bit value +Transaction.NLOCKTIME_MAX_VALUE = 4294967295; + /* Constructors and Serialization */ /** @@ -280,13 +286,41 @@ Transaction.prototype.fromObject = function(transaction) { }; /** - * sets nLockTime so that transaction is not valid until - * the desired date or block height + * Sets nLockTime so that transaction is not valid until the desired date(a + * timestamp in seconds since UNIX epoch is also accepted) + * * @param {Date | Number} time + * @return {Transaction} this */ -Transaction.prototype.lockUntil = function(time) { +Transaction.prototype.lockUntilDate = function(time) { $.checkArgument(time); - this.nLockTime = DEFAULT_NLOCKTIME; + if (_.isNumber(time) && time < Transaction.NLOCKTIME_BLOCKHEIGHT_LIMIT) { + throw new errors.Transaction.LockTimeTooEarly(); + } + if (_.isDate(time)) { + time = time.getTime() / 1000; + } + this.nLockTime = time; + return this; +}; + +/** + * Sets nLockTime so that transaction is not valid until the desired block + * height. + * + * @param {Number} time + * @return {Transaction} this + */ +Transaction.prototype.lockUntilBlockHeight = function(time) { + $.checkArgument(_.isNumber(time)); + if (time >= Transaction.NLOCKTIME_BLOCKHEIGHT_LIMIT) { + throw new errors.Transaction.BlockHeightTooHigh(); + } + if (time < 0) { + throw new errors.Transaction.NLockTimeOutOfRange(); + } + this.nLockTime = time; + return this; }; Transaction.prototype.toJSON = function toJSON() { diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index dbbd348..56bbee1 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -407,6 +407,46 @@ describe('Transaction', function() { transaction.outputs.length.should.equal(1); }); }); + + describe.only('setting the nLockTime', function() { + var MILLIS_IN_SECOND = 1000; + var timestamp = 1423504946; + var blockHeight = 342734; + var date = new Date(timestamp * MILLIS_IN_SECOND); + it('accepts a date instance', function() { + var transaction = new Transaction() + .lockUntilDate(date); + transaction.nLockTime.should.equal(timestamp); + }); + it('accepts a number instance with a timestamp', function() { + var transaction = new Transaction() + .lockUntilDate(timestamp); + transaction.nLockTime.should.equal(timestamp); + }); + it('accepts a block height', function() { + var transaction = new Transaction() + .lockUntilBlockHeight(blockHeight); + transaction.nLockTime.should.equal(blockHeight); + }); + it('fails if the block height is too high', function() { + expect(function() { + return new Transaction().lockUntilBlockHeight(5e8); + }).to.throw(errors.Transaction.BlockHeightTooHigh); + }); + it('fails if the date is too early', function() { + expect(function() { + return new Transaction().lockUntilDate(1); + }).to.throw(errors.Transaction.LockTimeTooEarly); + expect(function() { + return new Transaction().lockUntilDate(499999999); + }).to.throw(errors.Transaction.LockTimeTooEarly); + }); + it('fails if the date is negative', function() { + expect(function() { + return new Transaction().lockUntilBlockHeight(-1); + }).to.throw(errors.Transaction.NLockTimeOutOfRange); + }); + }); }); var tx_empty_hex = '01000000000000000000';