Merge pull request #31 from braydonf/rbf
Transaction: Added replace-by-fee (RBF) support
This commit is contained in:
commit
d36f72857b
|
@ -11,9 +11,10 @@ var Script = require('../../script');
|
||||||
var Sighash = require('../sighash');
|
var Sighash = require('../sighash');
|
||||||
var Output = require('../output');
|
var Output = require('../output');
|
||||||
|
|
||||||
|
var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1;
|
||||||
var DEFAULT_SEQNUMBER = 0xFFFFFFFF;
|
var DEFAULT_RBF_SEQNUMBER = MAXINT - 2;
|
||||||
var DEFAULT_LOCKTIME_SEQNUMBER = 0x00000000;
|
var DEFAULT_SEQNUMBER = MAXINT;
|
||||||
|
var DEFAULT_LOCKTIME_SEQNUMBER = MAXINT - 1;
|
||||||
|
|
||||||
function Input(params) {
|
function Input(params) {
|
||||||
if (!(this instanceof Input)) {
|
if (!(this instanceof Input)) {
|
||||||
|
@ -24,8 +25,10 @@ function Input(params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Input.MAXINT = MAXINT;
|
||||||
Input.DEFAULT_SEQNUMBER = DEFAULT_SEQNUMBER;
|
Input.DEFAULT_SEQNUMBER = DEFAULT_SEQNUMBER;
|
||||||
Input.DEFAULT_LOCKTIME_SEQNUMBER = DEFAULT_LOCKTIME_SEQNUMBER;
|
Input.DEFAULT_LOCKTIME_SEQNUMBER = DEFAULT_LOCKTIME_SEQNUMBER;
|
||||||
|
Input.DEFAULT_RBF_SEQNUMBER = DEFAULT_RBF_SEQNUMBER;
|
||||||
|
|
||||||
Object.defineProperty(Input.prototype, 'script', {
|
Object.defineProperty(Input.prototype, 'script', {
|
||||||
configurable: false,
|
configurable: false,
|
||||||
|
|
|
@ -546,7 +546,7 @@ Transaction.prototype.from = function(utxo, pubkeys, threshold) {
|
||||||
return input.prevTxId.toString('hex') === utxo.txId && input.outputIndex === utxo.outputIndex;
|
return input.prevTxId.toString('hex') === utxo.txId && input.outputIndex === utxo.outputIndex;
|
||||||
});
|
});
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return;
|
return this;
|
||||||
}
|
}
|
||||||
if (pubkeys && threshold) {
|
if (pubkeys && threshold) {
|
||||||
this._fromMultisigUtxo(utxo, pubkeys, threshold);
|
this._fromMultisigUtxo(utxo, pubkeys, threshold);
|
||||||
|
@ -1197,5 +1197,34 @@ Transaction.prototype.isCoinbase = function() {
|
||||||
return (this.inputs.length === 1 && this.inputs[0].isNull());
|
return (this.inputs.length === 1 && this.inputs[0].isNull());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this transaction can be replaced in the mempool with another
|
||||||
|
* transaction that provides a sufficiently higher fee (RBF).
|
||||||
|
*/
|
||||||
|
Transaction.prototype.isRBF = function() {
|
||||||
|
for (var i = 0; i < this.inputs.length; i++) {
|
||||||
|
var input = this.inputs[i];
|
||||||
|
if (input.sequenceNumber < Input.MAXINT - 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable this transaction to be replaced in the mempool (RBF) if a transaction
|
||||||
|
* includes a sufficiently higher fee. It will set the sequenceNumber to
|
||||||
|
* DEFAULT_RBF_SEQNUMBER for all inputs if the sequence number does not
|
||||||
|
* already enable RBF.
|
||||||
|
*/
|
||||||
|
Transaction.prototype.enableRBF = function() {
|
||||||
|
for (var i = 0; i < this.inputs.length; i++) {
|
||||||
|
var input = this.inputs[i];
|
||||||
|
if (input.sequenceNumber >= Input.MAXINT - 1) {
|
||||||
|
input.sequenceNumber = Input.DEFAULT_RBF_SEQNUMBER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Transaction;
|
module.exports = Transaction;
|
||||||
|
|
|
@ -214,7 +214,7 @@ describe('Transaction', function() {
|
||||||
var simpleUtxoWith1BTC = {
|
var simpleUtxoWith1BTC = {
|
||||||
address: fromAddress,
|
address: fromAddress,
|
||||||
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||||
outputIndex: 0,
|
outputIndex: 1,
|
||||||
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
||||||
satoshis: 1e8
|
satoshis: 1e8
|
||||||
};
|
};
|
||||||
|
@ -1120,6 +1120,106 @@ describe('Transaction', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('Replace-by-fee', function() {
|
||||||
|
describe('#enableRBF', function() {
|
||||||
|
it('only enable inputs not already enabled (0xffffffff)', function() {
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to([{address: toAddress, satoshis: 50000}])
|
||||||
|
.fee(15000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
tx.inputs[0].sequenceNumber = 0x00000000;
|
||||||
|
tx.enableRBF();
|
||||||
|
tx.inputs[0].sequenceNumber.should.equal(0x00000000);
|
||||||
|
tx.inputs[1].sequenceNumber.should.equal(0xfffffffd);
|
||||||
|
});
|
||||||
|
it('enable for inputs with 0xffffffff and 0xfffffffe', function() {
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to([{address: toAddress, satoshis: 50000}])
|
||||||
|
.fee(15000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
tx.inputs[0].sequenceNumber = 0xffffffff;
|
||||||
|
tx.inputs[1].sequenceNumber = 0xfffffffe;
|
||||||
|
tx.enableRBF();
|
||||||
|
tx.inputs[0].sequenceNumber.should.equal(0xfffffffd);
|
||||||
|
tx.inputs[1].sequenceNumber.should.equal(0xfffffffd);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#isRBF', function() {
|
||||||
|
it('enable and determine opt-in', function() {
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to([{address: toAddress, satoshis: 50000}])
|
||||||
|
.fee(15000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.enableRBF()
|
||||||
|
.sign(privateKey);
|
||||||
|
tx.isRBF().should.equal(true);
|
||||||
|
});
|
||||||
|
it('determine opt-out with default sequence number', function() {
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to([{address: toAddress, satoshis: 50000}])
|
||||||
|
.fee(15000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
tx.isRBF().should.equal(false);
|
||||||
|
});
|
||||||
|
it('determine opt-out with 0xfffffffe', function() {
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to([{address: toAddress, satoshis: 50000 + 1e8}])
|
||||||
|
.fee(15000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
tx.inputs[0].sequenceNumber = 0xfffffffe;
|
||||||
|
tx.inputs[1].sequenceNumber = 0xfffffffe;
|
||||||
|
tx.isRBF().should.equal(false);
|
||||||
|
});
|
||||||
|
it('determine opt-out with 0xffffffff', function() {
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to([{address: toAddress, satoshis: 50000 + 1e8}])
|
||||||
|
.fee(15000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
tx.inputs[0].sequenceNumber = 0xffffffff;
|
||||||
|
tx.inputs[1].sequenceNumber = 0xffffffff;
|
||||||
|
tx.isRBF().should.equal(false);
|
||||||
|
});
|
||||||
|
it('determine opt-in with 0xfffffffd (first input)', function() {
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to([{address: toAddress, satoshis: 50000 + 1e8}])
|
||||||
|
.fee(15000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
tx.inputs[0].sequenceNumber = 0xfffffffd;
|
||||||
|
tx.inputs[1].sequenceNumber = 0xffffffff;
|
||||||
|
tx.isRBF().should.equal(true);
|
||||||
|
});
|
||||||
|
it('determine opt-in with 0xfffffffd (second input)', function() {
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to([{address: toAddress, satoshis: 50000 + 1e8}])
|
||||||
|
.fee(15000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
tx.inputs[0].sequenceNumber = 0xffffffff;
|
||||||
|
tx.inputs[1].sequenceNumber = 0xfffffffd;
|
||||||
|
tx.isRBF().should.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue