Merge pull request #237 from matiu/feature/double-spends

Feature/double spends
This commit is contained in:
Matias Alejo Garcia 2014-02-11 15:58:49 -02:00
commit c4ebfa39ab
7 changed files with 174 additions and 53 deletions

View File

@ -35,6 +35,13 @@ module.exports = function(grunt) {
livereload: true,
},
},
js2: {
files: ['public/src/**/*.js'],
tasks: ['compile'],
options: {
livereload: true,
},
},
html: {
files: ['public/views/**'],
options: {
@ -124,7 +131,7 @@ module.exports = function(grunt) {
script: 'insight.js',
options: {
args: [],
ignore: ['public/**', 'test/**','util/**'],
ignore: ['public/**/*.html','public/**/*.css', 'public/**/*.js', 'test/**/*','util/**/*', ,'dev-util/**/*'],
// nodeArgs: ['--debug'],
delayTime: 1,
env: {

View File

@ -7,7 +7,6 @@
var Address = require('../models/Address'),
common = require('./common');
exports.address = function(req, res, next, addr) {

34
dev-util/level-put.js Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env node
'use strict';
var config = require('../config/config'),
levelup = require('levelup');
var k = process.argv[2];
var v = process.argv[3];
var isBlock = process.argv[4] === '1';
var dbPath = config.leveldb + (isBlock ? '/blocks' : '/txs');
console.log('DB: ',dbPath); //TODO
var db = levelup(dbPath );
if (v) {
db.put(k,v,function(err) {
console.log('[PUT done]',err); //TODO
});
}
else {
db.del(k,function(err) {
console.log('[DEL done]',err); //TODO
});
}

View File

@ -40,9 +40,6 @@ var walk = function(path) {
walk(models_path);
var syncOpts = {
};
/**
* p2pSync process
*/

View File

@ -51,7 +51,6 @@ function spec(b) {
};
// TransactionDb.prototype.fromTxIdOne = function(txid, cb) { TODO
TransactionDb.prototype.has = function(txid, cb) {
var k = OUTS_PREFIX + txid;
@ -135,6 +134,27 @@ function spec(b) {
});
};
TransactionDb.prototype._fillSpend = function(info, cb) {
var self = this;
if (!info) return cb();
var k = SPEND_PREFIX + info.txid;
db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) {
var k = data.key.split('-');
self._addSpendInfo(info.vout[k[3]], k[4], k[5]);
})
.on('error', function (err) {
return cb(err);
})
.on('end', function (err) {
return cb(err);
});
};
TransactionDb.prototype._fillOutpoints = function(info, cb) {
var self = this;
@ -142,18 +162,46 @@ function spec(b) {
var valueIn = 0;
var incompleteInputs = 0;
async.eachLimit(info.vin, CONCURRENCY, function(i, c_in) {
self.fromTxIdN(i.txid, i.vout, function(err, addr, valueSat) {
if (err || !addr || !valueSat ) {
self.fromTxIdN(i.txid, i.vout, function(err, ret) {
//console.log('[TransactionDb.js.154:ret:]',ret); //TODO
if (!ret || !ret.addr || !ret.valueSat ) {
console.log('Could not get TXouts in %s,%d from %s ', i.txid, i.vout, info.txid);
if (ret) i.unconfirmedInput = ret.unconfirmedInput;
incompleteInputs = 1;
return c_in(); // error not scalated
}
i.addr = addr;
i.valueSat = valueSat;
i.value = valueSat / util.COIN;
i.unconfirmedInput = i.unconfirmedInput;
i.addr = ret.addr;
i.valueSat = ret.valueSat;
i.value = ret.valueSat / util.COIN;
// Double spend?
if ( ret.multipleSpendAttempt ||
!ret.spendTxId ||
(ret.spendTxId && ret.spendTxId !== info.txid)
) {
if (ret.multipleSpendAttempts ) {
ret.multipleSpendAttempts.each(function(mul) {
if (mul.spendTxId !== info.txid) {
i.doubleSpendTxID = ret.spendTxId;
i.doubleSpendIndex = ret.spendIndex;
}
});
}
else if (!ret.spendTxId) {
i.dbError = 'Input spend not registered';
}
else {
i.doubleSpendTxID = ret.spendTxId;
i.doubleSpendIndex = ret.spendIndex;
}
}
else {
i.doubleSpendTxID = null;
}
valueIn += i.valueSat;
return c_in();
@ -178,7 +226,9 @@ function spec(b) {
if (err) return next(err);
self._fillOutpoints(info, function() {
return next(null, info);
self._fillSpend(info, function() {
return next(null, info);
});
});
});
};
@ -195,15 +245,33 @@ function spec(b) {
};
TransactionDb.prototype.fromTxIdN = function(txid, n, cb) {
var self = this;
var k = OUTS_PREFIX + txid + '-' + n;
db.get(k, function (err,val) {
if (err && err.notFound) {
err = null;
if (!val || (err && err.notFound) ) {
return cb(null, { unconfirmedInput: 1} );
}
var a = val?val.split(':'):[null,null];
return cb(err, a[0], parseInt(a[1]));
var a = val.split(':');
var ret = {
addr: a[0],
valueSat: parseInt(a[1]),
};
// Spend?
var k = SPEND_PREFIX + txid + '-' + n;
db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) {
var k = data.key.split('-');
self._addSpendInfo(ret, k[4], k[5]);
})
.on('error', function (error) {
return cb(error);
})
.on('end', function () {
return cb(null,ret);
});
});
};
@ -218,27 +286,18 @@ function spec(b) {
if (o.multipleSpendAttempts) {
var isConfirmed = 0;
var txid, index;
async.each(o.multipleSpendAttempts,
function (oi) {
async.each(o.multipleSpendAttempts,
function (oi, e_c) {
self.isConfirmed(oi.spendTxId, function(err,is) {
if (err) return cb(err);
isConfirmed = 1;
txid = oi.spendTxId;
index = oi.index;
return cb();
if (err) return;
if (is) {
o.spendTxId = oi.spendTxId;
o.index = oi.index;
o.spendIsConfirmed = 1;
}
return e_c();
});
},
function (err) {
// write the spended TXid into main register
if (isConfirmed) {
o.spendTxId = txid;
o.index = index;
o.spendIsConfirmed = 1;
}
return cb(err);
});
}, cb);
}
else {
self.isConfirmed(o.spendTxId, function(err,is) {
@ -367,7 +426,6 @@ function spec(b) {
TransactionDb.prototype.add = function(tx, blockhash, cb) {
var self = this;
var addrs = [];
var is_new = true;
if (tx.hash) self.adaptTxObject(tx);
@ -423,13 +481,8 @@ function spec(b) {
},
function (err) {
if (err) {
if (err.message.match(/E11000/)) {
is_new = false;
}
else {
console.log('ERR at TX %s: %s', tx.txid, err);
return cb(err);
}
console.log('ERR at TX %s: %s', tx.txid, err);
return cb(err);
}
return p_c();
});
@ -439,7 +492,7 @@ function spec(b) {
return self.setConfirmation(tx.txid,blockhash, true, p_c);
},
], function(err) {
return cb(err, addrs, is_new);
return cb(err, addrs);
});
};

View File

@ -19,12 +19,9 @@ function($scope, $rootScope, $routeParams, $location, Global, Transaction, Trans
var tmp = {};
var u = 0;
// TODO multiple output address
//
for(var i=0; i < l; i++) {
var notAddr = false;
// non standard input
if (items[i].scriptSig && !items[i].addr) {
items[i].addr = 'Unparsed address [' + u++ + ']';
@ -55,7 +52,12 @@ function($scope, $rootScope, $routeParams, $location, Global, Transaction, Trans
tmp[addr].addr = addr;
tmp[addr].items = [];
}
tmp[addr].isSpend = items[i].spendTxId;
tmp[addr].doubleSpendTxID = tmp[addr].doubleSpendTxID || items[i].doubleSpendTxID;
tmp[addr].doubleSpendIndex = tmp[addr].doubleSpendIndex || items[i].doubleSpendIndex;
tmp[addr].unconfirmedInput += items[i].unconfirmedInput;
tmp[addr].dbError = tmp[addr].dbError || items[i].dbError;
tmp[addr].valueSat += items[i].value * COIN;
tmp[addr].value = items[i].value;
tmp[addr].items.push(items[i]);

View File

@ -28,6 +28,11 @@
<span class="text-muted" title="Current Bitcoin Address" data-ng-show="vin.addr == $root.currentAddr">{{vin.addr}}</span>
<a href="/address/{{vin.addr}}" data-ng-show="!vin.notAddr && vin.addr != $root.currentAddr">{{vin.addr}}</a>
</div>
<div data-ng-show="vin.unconfirmedInput" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> (Input unconfirmed)</div>
<div data-ng-show="vin.dbError" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Incoherence in levelDB detected, please resync</div>
<div data-ng-show="vin.doubleSpendTxID" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Double spend attempt detected. From tx:
<a href="/tx/{{vin.doubleSpendTxID}}">{{vin.doubleSpendTxID}},{{vin.doubleSpendIndex}}</a>
</div>
</div>
</div>
<div data-ng-repeat="vin in tx.vin" data-ng-show="itemsExpanded">
@ -38,6 +43,12 @@
<span data-ng-show="vin.notAddr">{{vin.addr}}</span>
<a href="/address/{{vin.addr}}" data-ng-show="!vin.notAddr">{{vin.addr}}</a>
</div>
<div data-ng-show="vin.unconfirmedInput" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> (Input unconfirmed)</div>
<div data-ng-show="vin.dbError" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Incoherence in levelDB detected, please resync</div>
<div data-ng-show="vin.doubleSpendTxID" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Double spend attempt detected. From tx:
<a href="/tx/{{vin.doubleSpendTxID}}">{{vin.doubleSpendTxID}},{{vin.doubleSpendIndex}}</a>
</div>
</div>
<div class="col-md-12">
<div class="panel panel-default">
@ -62,17 +73,34 @@
<div class="row">
<div data-ng-repeat="vout in tx.voutSimple" data-ng-show="!itemsExpanded">
<div class="col-md-12 transaction-vin-vout">
<div class="text-muted pull-right btc-value" data-ng-class="{'text-success': $root.currentAddr == vout.addr}"><small>{{$root.currency.getConvertion(vout.value)}}</small></div>
<div class="text-muted pull-right btc-value" data-ng-class="{'text-success': $root.currentAddr == vout.addr}">
<small>
{{$root.currency.getConvertion(vout.value)}}
<span class="text-danger" data-ng-show="vout.isSpend" tooltip="Output is spend" tooltip-placement="left">(S)</span>
<span class="text-success" data-ng-show="!vout.isSpend" tooltip="Output is unspend" tooltip-placement="left">(U)</span>
</small>
</div>
<div class="ellipsis">
<span data-ng-show="vout.notAddr">{{vout.addr}}</span>
<span class="text-muted" title="Current Bitcoin Address" data-ng-show="address == $root.currentAddr" data-ng-repeat="address in vout.addr.split(',')">{{vout.addr}}</span>
<a href="/address/{{address}}" data-ng-show="!vout.notAddr && address != $root.currentAddr" data-ng-repeat="address in vout.addr.split(',')">{{address}}</a>
</div>
</div>
</div>
<div data-ng-repeat="vout in tx.vout" data-ng-show="itemsExpanded">
<div class="col-md-12 transaction-vin-vout">
<div class="text-muted pull-right btc-value"><small>{{$root.currency.getConvertion(vout.value)}}</small></div>
<div class="text-muted pull-right btc-value"><small>{{$root.currency.getConvertion(vout.value)}}
<span class="text-success" data-ng-show="!vout.spendTxId" tooltip="Output is unspend" tooltip-placement="left">(U)</span>
<a class="glyphicon glyphicon-chevron-right" data-ng-show="vout.spendTxId" href="/#!/tx/{{vout.spendTxId}}" title="Spent at: {{vout.spendTxId}},{{vout.spendIndex}}"></a>&nbsp;&nbsp;
</small>
</div>
<div class="ellipsis">
<a href="/address/{{address}}" data-ng-repeat="address in vout.scriptPubKey.addresses">{{address}}</a>
</div>
@ -108,7 +136,8 @@
</div>
<div class="line-top row">
<div class="col-xs-4 col-md-6">
<span data-ng-show="!tx.isCoinBase" class="label label-default">Fees: {{$root.currency.getConvertion(tx.fees)}}</span>
<span data-ng-show="!tx.isCoinBase && tx.feeds" class="label label-default">Fees: {{$root.currency.getConvertion(tx.fees)}}</span>
<span data-ng-show="!tx.isCoinBase && !tx.feeds" class="label label-default">Fees: unknown yet</span>
</div>
<div class="col-xs-8 col-md-6 text-right">
<span data-ng-show="tx.confirmations" class="label label-success">{{tx.confirmations}} Confirmations</span>