TransactionBuilder: add fee safety
This commit is contained in:
parent
11079bfafb
commit
e835f1fe95
4 changed files with 67 additions and 22 deletions
|
@ -272,7 +272,10 @@ TransactionBuilder.fromTransaction = function (transaction, network) {
|
||||||
|
|
||||||
// Copy inputs
|
// Copy inputs
|
||||||
transaction.ins.forEach(function (txIn) {
|
transaction.ins.forEach(function (txIn) {
|
||||||
txb.__addInputUnsafe(txIn.hash, txIn.index, txIn.sequence, txIn.script)
|
txb.__addInputUnsafe(txIn.hash, txIn.index, {
|
||||||
|
sequence: txIn.sequence,
|
||||||
|
script: txIn.script
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// fix some things not possible through the public API
|
// fix some things not possible through the public API
|
||||||
|
@ -288,6 +291,8 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu
|
||||||
throw new Error('No, this would invalidate signatures')
|
throw new Error('No, this would invalidate signatures')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var value
|
||||||
|
|
||||||
// is it a hex string?
|
// is it a hex string?
|
||||||
if (typeof txHash === 'string') {
|
if (typeof txHash === 'string') {
|
||||||
// transaction hashs's are displayed in reverse order, un-reverse it
|
// transaction hashs's are displayed in reverse order, un-reverse it
|
||||||
|
@ -295,14 +300,21 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu
|
||||||
|
|
||||||
// is it a Transaction object?
|
// is it a Transaction object?
|
||||||
} else if (txHash instanceof Transaction) {
|
} else if (txHash instanceof Transaction) {
|
||||||
prevOutScript = txHash.outs[vout].script
|
var txOut = txHash.outs[vout]
|
||||||
|
prevOutScript = txOut.script
|
||||||
|
value = txOut.value
|
||||||
|
|
||||||
txHash = txHash.getHash()
|
txHash = txHash.getHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.__addInputUnsafe(txHash, vout, sequence, null, prevOutScript)
|
return this.__addInputUnsafe(txHash, vout, {
|
||||||
|
sequence: sequence,
|
||||||
|
prevOutScript: prevOutScript,
|
||||||
|
value: value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence, scriptSig, prevOutScript) {
|
TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, options) {
|
||||||
if (Transaction.isCoinbaseHash(txHash)) {
|
if (Transaction.isCoinbaseHash(txHash)) {
|
||||||
throw new Error('coinbase inputs not supported')
|
throw new Error('coinbase inputs not supported')
|
||||||
}
|
}
|
||||||
|
@ -313,16 +325,21 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence
|
||||||
var input = {}
|
var input = {}
|
||||||
|
|
||||||
// derive what we can from the scriptSig
|
// derive what we can from the scriptSig
|
||||||
if (scriptSig) {
|
if (options.script !== undefined) {
|
||||||
input = expandInput(scriptSig)
|
input = expandInput(options.script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if an input value was given, retain it
|
||||||
|
if (options.value !== undefined) {
|
||||||
|
input.value = options.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// derive what we can from the previous transactions output script
|
// derive what we can from the previous transactions output script
|
||||||
if (!input.prevOutScript && prevOutScript) {
|
if (!input.prevOutScript && options.prevOutScript) {
|
||||||
var prevOutType
|
var prevOutType
|
||||||
|
|
||||||
if (!input.pubKeys && !input.signatures) {
|
if (!input.pubKeys && !input.signatures) {
|
||||||
var expanded = expandOutput(prevOutScript)
|
var expanded = expandOutput(options.prevOutScript)
|
||||||
|
|
||||||
if (expanded.pubKeys) {
|
if (expanded.pubKeys) {
|
||||||
input.pubKeys = expanded.pubKeys
|
input.pubKeys = expanded.pubKeys
|
||||||
|
@ -332,11 +349,11 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence
|
||||||
prevOutType = expanded.scriptType
|
prevOutType = expanded.scriptType
|
||||||
}
|
}
|
||||||
|
|
||||||
input.prevOutScript = prevOutScript
|
input.prevOutScript = options.prevOutScript
|
||||||
input.prevOutType = prevOutType || bscript.classifyOutput(prevOutScript)
|
input.prevOutType = prevOutType || bscript.classifyOutput(options.prevOutScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
var vin = this.tx.addInput(txHash, vout, sequence, scriptSig)
|
var vin = this.tx.addInput(txHash, vout, options.sequence, options.scriptSig)
|
||||||
this.inputs[vin] = input
|
this.inputs[vin] = input
|
||||||
this.prevTxMap[prevTxOut] = true
|
this.prevTxMap[prevTxOut] = true
|
||||||
|
|
||||||
|
@ -367,6 +384,9 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
|
||||||
if (!allowIncomplete) {
|
if (!allowIncomplete) {
|
||||||
if (!this.tx.ins.length) throw new Error('Transaction has no inputs')
|
if (!this.tx.ins.length) throw new Error('Transaction has no inputs')
|
||||||
if (!this.tx.outs.length) throw new Error('Transaction has no outputs')
|
if (!this.tx.outs.length) throw new Error('Transaction has no outputs')
|
||||||
|
|
||||||
|
// do not rely on this, its merely a last resort
|
||||||
|
if (this.__hasAbsurdFee()) throw new Error('Transaction has absurd fees')
|
||||||
}
|
}
|
||||||
|
|
||||||
var tx = this.tx.clone()
|
var tx = this.tx.clone()
|
||||||
|
@ -479,4 +499,18 @@ TransactionBuilder.prototype.__canModifyOutputs = function () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransactionBuilder.prototype.__hasAbsurdFee = function () {
|
||||||
|
// not all inputs will have .value defined
|
||||||
|
var incoming = this.inputs.reduce(function (a, x) { return a + (x.value >>> 0) }, 0)
|
||||||
|
|
||||||
|
// but all outputs do, and if we have any input value
|
||||||
|
// we can immediately determine if the outputs are too small
|
||||||
|
var outgoing = this.tx.outs.reduce(function (a, x) { return a + x.value }, 0)
|
||||||
|
var fee = incoming - outgoing
|
||||||
|
|
||||||
|
// its not fool-proof, but, it might help somebody
|
||||||
|
// fee > 0.2BTC
|
||||||
|
return fee > (0.2 * 1e8)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = TransactionBuilder
|
module.exports = TransactionBuilder
|
||||||
|
|
|
@ -10,7 +10,7 @@ function BIP32Path (value) {
|
||||||
}
|
}
|
||||||
BIP32Path.toJSON = function () { return 'BIP32 derivation path' }
|
BIP32Path.toJSON = function () { return 'BIP32 derivation path' }
|
||||||
|
|
||||||
var SATOSHI_MAX = 2.1 * 1e15
|
var SATOSHI_MAX = 21 * 1e14
|
||||||
function Satoshi (value) {
|
function Satoshi (value) {
|
||||||
return typeforce.UInt53(value) && value <= SATOSHI_MAX
|
return typeforce.UInt53(value) && value <= SATOSHI_MAX
|
||||||
}
|
}
|
||||||
|
|
30
test/fixtures/transaction_builder.json
vendored
30
test/fixtures/transaction_builder.json
vendored
|
@ -657,20 +657,33 @@
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
"vout": 0,
|
"vout": 0
|
||||||
"signs": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": []
|
"outputs": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"exception": "Transaction has absurd fees",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"txHex": "01000000000100e1f505000000000000000000",
|
||||||
|
"vout": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"script": "OP_DUP OP_HASH160 ff99e06c1a4ac394b4e1cb3d3a4b2b47749e339a OP_EQUALVERIFY OP_CHECKSIG",
|
||||||
|
"value": 100000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Incomplete transaction, nothing assumed",
|
"description": "Incomplete transaction, nothing assumed",
|
||||||
"exception": "Transaction is not complete",
|
"exception": "Transaction is not complete",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
"vout": 0,
|
"vout": 0
|
||||||
"signs": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
|
@ -697,8 +710,7 @@
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
"prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG",
|
"prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG"
|
||||||
"signs": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
|
@ -715,13 +727,11 @@
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
"vout": 0,
|
"vout": 0
|
||||||
"signs": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
"vout": 0,
|
"vout": 0
|
||||||
"signs": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
|
|
|
@ -37,6 +37,7 @@ function construct (f, sign) {
|
||||||
if (sign === false) return txb
|
if (sign === false) return txb
|
||||||
|
|
||||||
f.inputs.forEach(function (input, index) {
|
f.inputs.forEach(function (input, index) {
|
||||||
|
if (!input.signs) return
|
||||||
input.signs.forEach(function (sign) {
|
input.signs.forEach(function (sign) {
|
||||||
var keyPair = ECPair.fromWIF(sign.keyPair, network)
|
var keyPair = ECPair.fromWIF(sign.keyPair, network)
|
||||||
var redeemScript
|
var redeemScript
|
||||||
|
|
Loading…
Reference in a new issue