Merge pull request #209 from weilu/pending-utxo
Allow marking utxo as pending
This commit is contained in:
commit
a98391572a
2 changed files with 105 additions and 94 deletions
|
@ -68,7 +68,7 @@ function Wallet(seed, network) {
|
||||||
|
|
||||||
for(var key in this.outputs){
|
for(var key in this.outputs){
|
||||||
var output = this.outputs[key]
|
var output = this.outputs[key]
|
||||||
if(!output.spend) utxo.push(outputToUnspentOutput(output))
|
utxo.push(outputToUnspentOutput(output))
|
||||||
}
|
}
|
||||||
|
|
||||||
return utxo
|
return utxo
|
||||||
|
@ -103,7 +103,8 @@ function Wallet(seed, network) {
|
||||||
return {
|
return {
|
||||||
receive: key,
|
receive: key,
|
||||||
address: o.address,
|
address: o.address,
|
||||||
value: o.value
|
value: o.value,
|
||||||
|
pending: o.pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +137,15 @@ function Wallet(seed, network) {
|
||||||
return value == undefined
|
return value == undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
this.processTx = function(tx) {
|
this.processPendingTx = function(tx){
|
||||||
|
processTx(tx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.processConfirmedTx = function(tx){
|
||||||
|
processTx(tx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function processTx(tx, isPending) {
|
||||||
var txhash = tx.getHash()
|
var txhash = tx.getHash()
|
||||||
|
|
||||||
tx.outs.forEach(function(txOut, i){
|
tx.outs.forEach(function(txOut, i){
|
||||||
|
@ -155,17 +164,16 @@ function Wallet(seed, network) {
|
||||||
receive: output,
|
receive: output,
|
||||||
value: txOut.value,
|
value: txOut.value,
|
||||||
address: address,
|
address: address,
|
||||||
|
pending: isPending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
tx.ins.forEach(function(txIn, i){
|
tx.ins.forEach(function(txIn, i){
|
||||||
var op = txIn.outpoint
|
var op = txIn.outpoint
|
||||||
|
var output = op.hash + ':' + op.index
|
||||||
|
|
||||||
var o = me.outputs[op.hash + ':' + op.index]
|
if(me.outputs[output]) delete me.outputs[output]
|
||||||
if (o) {
|
|
||||||
o.spend = txhash + ':' + i
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +218,7 @@ function Wallet(seed, network) {
|
||||||
|
|
||||||
for (var key in me.outputs) {
|
for (var key in me.outputs) {
|
||||||
var output = me.outputs[key]
|
var output = me.outputs[key]
|
||||||
if (!output.spend) unspent.push(output)
|
if (!output.pending) unspent.push(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortByValueDesc = unspent.sort(function(o1, o2){
|
var sortByValueDesc = unspent.sort(function(o1, o2){
|
||||||
|
|
175
test/wallet.js
175
test/wallet.js
|
@ -195,14 +195,6 @@ describe('Wallet', function() {
|
||||||
|
|
||||||
assert.equal(wallet.getBalance(), 40000)
|
assert.equal(wallet.getBalance(), 40000)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('excludes spent outputs', function(){
|
|
||||||
addUtxoToOutput(expectedUtxo)
|
|
||||||
addUtxoToOutput(utxo1)
|
|
||||||
wallet.outputs[utxo1.hash + ':' + utxo1.outputIndex].spend = "sometxn:m"
|
|
||||||
|
|
||||||
assert.equal(wallet.getBalance(), 20000)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getUnspentOutputs', function(){
|
describe('getUnspentOutputs', function(){
|
||||||
|
@ -213,11 +205,6 @@ describe('Wallet', function() {
|
||||||
it('parses wallet outputs to the expect format', function(){
|
it('parses wallet outputs to the expect format', function(){
|
||||||
assert.deepEqual(wallet.getUnspentOutputs(), [expectedUtxo])
|
assert.deepEqual(wallet.getUnspentOutputs(), [expectedUtxo])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('excludes spent outputs', function(){
|
|
||||||
wallet.outputs[expectedOutputKey].spend = "sometxn:m"
|
|
||||||
assert.deepEqual(wallet.getUnspentOutputs(), [])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('setUnspentOutputs', function(){
|
describe('setUnspentOutputs', function(){
|
||||||
|
@ -252,7 +239,7 @@ describe('Wallet', function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('processTx', function(){
|
describe('Process transaction', function(){
|
||||||
var addresses
|
var addresses
|
||||||
var tx
|
var tx
|
||||||
|
|
||||||
|
@ -266,90 +253,105 @@ describe('Wallet', function() {
|
||||||
tx = Transaction.fromHex(fixtureTx1Hex)
|
tx = Transaction.fromHex(fixtureTx1Hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not fail on scripts with no corresponding Address', function() {
|
describe("processPendingTx", function(){
|
||||||
var pubKey = wallet.getPrivateKey(0).pub
|
it("sets the pending flag on output", function(){
|
||||||
var script = Script.createPubKeyScriptPubKey(pubKey)
|
|
||||||
var tx2 = new Transaction()
|
|
||||||
tx2.addInput(fakeTxHash(1), 0)
|
|
||||||
|
|
||||||
// FIXME: Transaction doesn't support custom ScriptPubKeys... yet
|
|
||||||
// So for now, we hijack the script with our own, and undefine the cached address
|
|
||||||
tx2.addOutput(addresses[0], 10000)
|
|
||||||
tx2.outs[0].script = script
|
|
||||||
tx2.outs[0].address = undefined
|
|
||||||
|
|
||||||
wallet.processTx(tx2)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){
|
|
||||||
it("works for receive address", function(){
|
|
||||||
var totalOuts = outputCount()
|
|
||||||
|
|
||||||
wallet.addresses = [addresses[0]]
|
wallet.addresses = [addresses[0]]
|
||||||
wallet.processTx(tx)
|
wallet.processPendingTx(tx)
|
||||||
|
|
||||||
assert.equal(outputCount(), totalOuts + 1)
|
verifyOutputAdded(0, true)
|
||||||
verifyOutputAdded(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("works for change address", function(){
|
|
||||||
var totalOuts = outputCount()
|
|
||||||
wallet.changeAddresses = [addresses[1]]
|
|
||||||
|
|
||||||
wallet.processTx(tx)
|
|
||||||
|
|
||||||
assert.equal(outputCount(), totalOuts + 1)
|
|
||||||
verifyOutputAdded(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
function outputCount(){
|
|
||||||
return Object.keys(wallet.outputs).length
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyOutputAdded(index) {
|
|
||||||
var txOut = tx.outs[index]
|
|
||||||
var key = tx.getHash() + ":" + index
|
|
||||||
var output = wallet.outputs[key]
|
|
||||||
assert.equal(output.receive, key)
|
|
||||||
assert.equal(output.value, txOut.value)
|
|
||||||
|
|
||||||
var txOutAddress = Address.fromScriptPubKey(txOut.script).toString()
|
|
||||||
assert.equal(output.address, txOutAddress)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("when tx ins outpoint contains a known txhash:i, the corresponding 'output' gets updated", function(){
|
describe('processConfirmedTx', function(){
|
||||||
beforeEach(function(){
|
|
||||||
wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input
|
|
||||||
wallet.processTx(tx)
|
|
||||||
|
|
||||||
tx = Transaction.fromHex(fixtureTx2Hex)
|
it('does not fail on scripts with no corresponding Address', function() {
|
||||||
|
var pubKey = wallet.getPrivateKey(0).pub
|
||||||
|
var script = Script.createPubKeyScriptPubKey(pubKey)
|
||||||
|
var tx2 = new Transaction()
|
||||||
|
tx2.addInput(fakeTxHash(1), 0)
|
||||||
|
|
||||||
|
// FIXME: Transaction doesn't support custom ScriptPubKeys... yet
|
||||||
|
// So for now, we hijack the script with our own, and undefine the cached address
|
||||||
|
tx2.addOutput(addresses[0], 10000)
|
||||||
|
tx2.outs[0].script = script
|
||||||
|
tx2.outs[0].address = undefined
|
||||||
|
|
||||||
|
wallet.processConfirmedTx(tx2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("does not add to wallet.outputs", function(){
|
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){
|
||||||
|
it("works for receive address", function(){
|
||||||
|
var totalOuts = outputCount()
|
||||||
|
|
||||||
|
wallet.addresses = [addresses[0]]
|
||||||
|
wallet.processConfirmedTx(tx)
|
||||||
|
|
||||||
|
assert.equal(outputCount(), totalOuts + 1)
|
||||||
|
verifyOutputAdded(0, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("works for change address", function(){
|
||||||
|
var totalOuts = outputCount()
|
||||||
|
wallet.changeAddresses = [addresses[1]]
|
||||||
|
|
||||||
|
wallet.processConfirmedTx(tx)
|
||||||
|
|
||||||
|
assert.equal(outputCount(), totalOuts + 1)
|
||||||
|
verifyOutputAdded(1, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
function outputCount(){
|
||||||
|
return Object.keys(wallet.outputs).length
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("when tx ins outpoint contains a known txhash:i", function(){
|
||||||
|
beforeEach(function(){
|
||||||
|
wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input
|
||||||
|
wallet.processConfirmedTx(tx)
|
||||||
|
|
||||||
|
tx = Transaction.fromHex(fixtureTx2Hex)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not add to wallet.outputs", function(){
|
||||||
|
var outputs = wallet.outputs
|
||||||
|
wallet.processConfirmedTx(tx)
|
||||||
|
assert.deepEqual(wallet.outputs, outputs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("deletes corresponding 'output'", function(){
|
||||||
|
wallet.processConfirmedTx(tx)
|
||||||
|
|
||||||
|
var txIn = tx.ins[0]
|
||||||
|
var key = txIn.outpoint.hash + ":" + txIn.outpoint.index
|
||||||
|
var output = wallet.outputs[key]
|
||||||
|
|
||||||
|
assert.equal(output, undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does nothing when none of the involved addresses belong to the wallet", function(){
|
||||||
var outputs = wallet.outputs
|
var outputs = wallet.outputs
|
||||||
wallet.processTx(tx)
|
wallet.processConfirmedTx(tx)
|
||||||
assert.deepEqual(wallet.outputs, outputs)
|
assert.deepEqual(wallet.outputs, outputs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("sets spend with the transaction hash and input index", function(){
|
|
||||||
wallet.processTx(tx)
|
|
||||||
|
|
||||||
var txIn = tx.ins[0]
|
|
||||||
var key = txIn.outpoint.hash + ":" + txIn.outpoint.index
|
|
||||||
var output = wallet.outputs[key]
|
|
||||||
|
|
||||||
assert.equal(output.spend, tx.getHash() + ':' + 0)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("does nothing when none of the involved addresses belong to the wallet", function(){
|
function verifyOutputAdded(index, pending) {
|
||||||
var outputs = wallet.outputs
|
var txOut = tx.outs[index]
|
||||||
wallet.processTx(tx)
|
var key = tx.getHash() + ":" + index
|
||||||
assert.deepEqual(wallet.outputs, outputs)
|
var output = wallet.outputs[key]
|
||||||
})
|
assert.equal(output.receive, key)
|
||||||
|
assert.equal(output.value, txOut.value)
|
||||||
|
assert.equal(output.pending, pending)
|
||||||
|
|
||||||
|
var txOutAddress = Address.fromScriptPubKey(txOut.script).toString()
|
||||||
|
assert.equal(output.address, txOutAddress)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
describe('createTx', function(){
|
describe('createTx', function(){
|
||||||
var to, value
|
var to, value
|
||||||
var address1, address2
|
var address1, address2
|
||||||
|
@ -412,23 +414,24 @@ describe('Wallet', function() {
|
||||||
assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 })
|
assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('ignores spent outputs', function(){
|
it('ignores pending outputs', function(){
|
||||||
utxo.push(
|
utxo.push(
|
||||||
{
|
{
|
||||||
"hash": fakeTxHash(4),
|
"hash": fakeTxHash(4),
|
||||||
"outputIndex": 0,
|
"outputIndex": 0,
|
||||||
"address" : address2,
|
"address" : address2,
|
||||||
"value": 530000 // enough but spent before createTx
|
"value": 530000,
|
||||||
|
"pending": true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
wallet.setUnspentOutputs(utxo)
|
wallet.setUnspentOutputs(utxo)
|
||||||
wallet.outputs[fakeTxHash(4) + ":" + 0].spend = fakeTxHash(5) + ":" + 0
|
|
||||||
|
|
||||||
var tx = wallet.createTx(to, value)
|
var tx = wallet.createTx(to, value)
|
||||||
|
|
||||||
assert.equal(tx.ins.length, 1)
|
assert.equal(tx.ins.length, 1)
|
||||||
assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 })
|
assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 })
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe(networks.testnet, function(){
|
describe(networks.testnet, function(){
|
||||||
|
|
Loading…
Reference in a new issue