Merge pull request #107 from matiu/bug/fix-double-spent2
Bug/fix double spent2
This commit is contained in:
commit
2e060916d5
20
README.md
20
README.md
@ -26,14 +26,16 @@ Alternatively, a total resync can be made, running `$ util/sync.js -D`
|
|||||||
## IMPORTANT: v0.2 Caching schema
|
## IMPORTANT: v0.2 Caching schema
|
||||||
|
|
||||||
In v0.2 a new cache schema has been introduced. Only information from transactions with
|
In v0.2 a new cache schema has been introduced. Only information from transactions with
|
||||||
SAFE_CONFIRMATIONS+ settings will be cached (by default SAFE_CONFIRMATIONS=6). There
|
INSIGHT_SAFE_CONFIRMATIONS+ settings will be cached (by default SAFE_CONFIRMATIONS=6). There
|
||||||
are 3 different caches:
|
are 3 different caches:
|
||||||
* nr. of confirmations
|
* nr. of confirmations
|
||||||
* transaction spent information
|
* transaction spent information
|
||||||
* scriptPubKey for unspent transactions
|
* scriptPubKey for unspent transactions
|
||||||
|
|
||||||
Cache data is only completed on request, i.e., only after accessing the required data for
|
Cache data is only populated on request, i.e., only after accessing the required data for
|
||||||
the first time, the information is cached, there is not pre-caching procedure.
|
the first time, the information is cached, there is not pre-caching procedure. To ignore
|
||||||
|
cache by default, use INSIGHT_IGNORE_CACHE. Also, address related calls support `?noCache=1`
|
||||||
|
to ignore the cache in a particular API request.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@ -91,7 +93,9 @@ BITCOIND_PASS # RPC password
|
|||||||
BITCOIND_DATADIR # bitcoind datadir for livenet, or datadir/testnet3 for testnet
|
BITCOIND_DATADIR # bitcoind datadir for livenet, or datadir/testnet3 for testnet
|
||||||
INSIGHT_NETWORK [= 'livenet' | 'testnet']
|
INSIGHT_NETWORK [= 'livenet' | 'testnet']
|
||||||
INSIGHT_DB # Path where to store insight's internal DB. (defaults to $HOME/.insight)
|
INSIGHT_DB # Path where to store insight's internal DB. (defaults to $HOME/.insight)
|
||||||
SAFE_CONFIRMATIONS=6 # Nr. of confirmation needed to start caching transaction information
|
INSIGHT_SAFE_CONFIRMATIONS=6 # Nr. of confirmation needed to start caching transaction information
|
||||||
|
INSIGHT_IGNORE_CACHE # True to ignore cache of spents in transaction, with more than INSIGHT_SAFE_CONFIRMATIONS confirmations. This is useful for tracking double spents for old transactions.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure that bitcoind is configured to [accept incoming connections using 'rpcallowip'](https://en.bitcoin.it/wiki/Running_Bitcoin).
|
Make sure that bitcoind is configured to [accept incoming connections using 'rpcallowip'](https://en.bitcoin.it/wiki/Running_Bitcoin).
|
||||||
@ -163,12 +167,12 @@ The end-points are:
|
|||||||
```
|
```
|
||||||
### Address
|
### Address
|
||||||
```
|
```
|
||||||
/api/addr/[:addr][?noTxList=1]
|
/api/addr/[:addr][?noTxList=1&noCache=1]
|
||||||
/api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?noTxList=1
|
/api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?noTxList=1
|
||||||
```
|
```
|
||||||
### Unspent Outputs
|
### Unspent Outputs
|
||||||
```
|
```
|
||||||
/api/addr/[:addr]/utxo
|
/api/addr/[:addr]/utxo[?noCache=1]
|
||||||
```
|
```
|
||||||
Sample return:
|
Sample return:
|
||||||
``` json
|
``` json
|
||||||
@ -193,8 +197,8 @@ Sample return:
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
Please not that in case confirmations are cached and are more that SAFE_CONFIRMATIONS setting, the
|
Please not that in case confirmations are cached and are more that INSIGHT_SAFE_CONFIRMATIONS setting, the
|
||||||
return can be a string of the form 'SAFE_CONFIRMATIONS+'
|
return can be a string of the form `SAFE_CONFIRMATIONS+`, e.g.: the string `6+`
|
||||||
|
|
||||||
|
|
||||||
### Unspent Outputs for multiple addresses
|
### Unspent Outputs for multiple addresses
|
||||||
|
|||||||
@ -53,7 +53,7 @@ exports.show = function(req, res, next) {
|
|||||||
} else {
|
} else {
|
||||||
return res.jsonp(a.getObj());
|
return res.jsonp(a.getObj());
|
||||||
}
|
}
|
||||||
}, {txLimit: req.query.noTxList?0:-1});
|
}, {txLimit: req.query.noTxList?0:-1, ignoreCache: req.param('noCache')});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ exports.utxo = function(req, res, next) {
|
|||||||
else {
|
else {
|
||||||
return res.jsonp(a.unspent);
|
return res.jsonp(a.unspent);
|
||||||
}
|
}
|
||||||
}, {onlyUnspent: 1});
|
}, {onlyUnspent:1, ignoreCache: req.param('noCache')});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ exports.multiutxo = function(req, res, next) {
|
|||||||
if (err) callback(err);
|
if (err) callback(err);
|
||||||
utxos = utxos.concat(a.unspent);
|
utxos = utxos.concat(a.unspent);
|
||||||
callback();
|
callback();
|
||||||
}, {onlyUnspent:1});
|
}, {onlyUnspent:1, ignoreCache: req.param('noCache')});
|
||||||
}, function(err) { // finished callback
|
}, function(err) { // finished callback
|
||||||
if (err) return common.handleErrors(err, res);
|
if (err) return common.handleErrors(err, res);
|
||||||
res.jsonp(utxos);
|
res.jsonp(utxos);
|
||||||
@ -99,7 +99,7 @@ exports.balance = function(req, res, next) {
|
|||||||
} else {
|
} else {
|
||||||
return res.jsonp(a.balanceSat);
|
return res.jsonp(a.balanceSat);
|
||||||
}
|
}
|
||||||
});
|
}, {ignoreCache: req.param('noCache')});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.totalReceived = function(req, res, next) {
|
exports.totalReceived = function(req, res, next) {
|
||||||
@ -111,7 +111,7 @@ exports.totalReceived = function(req, res, next) {
|
|||||||
} else {
|
} else {
|
||||||
return res.jsonp(a.totalReceivedSat);
|
return res.jsonp(a.totalReceivedSat);
|
||||||
}
|
}
|
||||||
});
|
}, {ignoreCache: req.param('noCache')});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.totalSent = function(req, res, next) {
|
exports.totalSent = function(req, res, next) {
|
||||||
@ -123,7 +123,7 @@ exports.totalSent = function(req, res, next) {
|
|||||||
} else {
|
} else {
|
||||||
return res.jsonp(a.totalSentSat);
|
return res.jsonp(a.totalSentSat);
|
||||||
}
|
}
|
||||||
});
|
}, {ignoreCache: req.param('noCache')});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.unconfirmedBalance = function(req, res, next) {
|
exports.unconfirmedBalance = function(req, res, next) {
|
||||||
@ -135,5 +135,5 @@ exports.unconfirmedBalance = function(req, res, next) {
|
|||||||
} else {
|
} else {
|
||||||
return res.jsonp(a.unconfirmedBalanceSat);
|
return res.jsonp(a.unconfirmedBalanceSat);
|
||||||
}
|
}
|
||||||
});
|
}, {ignoreCache: req.param('noCache')});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -149,10 +149,15 @@ Address.prototype.update = function(next, opts) {
|
|||||||
if (!self.addrStr) return next();
|
if (!self.addrStr) return next();
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
|
if (! ('ignoreCache' in opts) )
|
||||||
|
opts.ignoreCache = config.ignoreCache;
|
||||||
|
|
||||||
|
// should collect txList from address?
|
||||||
var txList = opts.txLimit === 0 ? null: [];
|
var txList = opts.txLimit === 0 ? null: [];
|
||||||
|
|
||||||
var tDb = TransactionDb;
|
var tDb = TransactionDb;
|
||||||
var bDb = BlockDb;
|
var bDb = BlockDb;
|
||||||
tDb.fromAddr(self.addrStr, function(err,txOut){
|
tDb.fromAddr(self.addrStr, opts, function(err,txOut){
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
bDb.fillConfirmations(txOut, function(err) {
|
bDb.fillConfirmations(txOut, function(err) {
|
||||||
@ -184,7 +189,9 @@ Address.prototype.update = function(next, opts) {
|
|||||||
txOut.forEach(function(txItem){
|
txOut.forEach(function(txItem){
|
||||||
self._addTxItem(txItem, txList);
|
self._addTxItem(txItem, txList);
|
||||||
});
|
});
|
||||||
if (txList) self.transactions = txList;
|
if (txList)
|
||||||
|
self.transactions = txList;
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -60,7 +60,8 @@ if (!dataDir) {
|
|||||||
}
|
}
|
||||||
dataDir += network === 'testnet' ? 'testnet3' : '';
|
dataDir += network === 'testnet' ? 'testnet3' : '';
|
||||||
|
|
||||||
var safeConfirmations = process.env.SAFE_CONFIRMATIONS || 6;
|
var safeConfirmations = process.env.INSIGHT_SAFE_CONFIRMATIONS || 6;
|
||||||
|
var ignoreCache = process.env.INSIGHT_IGNORE_CACHE || 0;
|
||||||
|
|
||||||
|
|
||||||
var bitcoindConf = {
|
var bitcoindConf = {
|
||||||
@ -88,7 +89,8 @@ console.log(
|
|||||||
# Configuration:\n\
|
# Configuration:\n\
|
||||||
\t\tNetwork: %s\tINSIGHT_NETWORK\n\
|
\t\tNetwork: %s\tINSIGHT_NETWORK\n\
|
||||||
\t\tDatabase Path: %s\tINSIGHT_DB\n\
|
\t\tDatabase Path: %s\tINSIGHT_DB\n\
|
||||||
\t\tSafe Confirmations: %s\tSAFE_CONFIRMATIONS\n\
|
\t\tSafe Confirmations: %s\tINSIGHT_SAFE_CONFIRMATIONS\n\
|
||||||
|
\t\tIgnore Cache: %s\tINSIGHT_IGNORE_CACHE\n\
|
||||||
# Bicoind Connection configuration:\n\
|
# Bicoind Connection configuration:\n\
|
||||||
\t\tRPC Username: %s\tBITCOIND_USER\n\
|
\t\tRPC Username: %s\tBITCOIND_USER\n\
|
||||||
\t\tRPC Password: %s\tBITCOIND_PASS\n\
|
\t\tRPC Password: %s\tBITCOIND_PASS\n\
|
||||||
@ -102,7 +104,7 @@ console.log(
|
|||||||
$ INSIGHT_NETWORK="testnet" BITCOIND_HOST="123.123.123.123" ./insight.js\
|
$ INSIGHT_NETWORK="testnet" BITCOIND_HOST="123.123.123.123" ./insight.js\
|
||||||
\n\n',
|
\n\n',
|
||||||
version,
|
version,
|
||||||
network, home, safeConfirmations,
|
network, home, safeConfirmations, ignoreCache?'yes':'no',
|
||||||
bitcoindConf.user,
|
bitcoindConf.user,
|
||||||
bitcoindConf.pass?'Yes(hidden)':'No',
|
bitcoindConf.pass?'Yes(hidden)':'No',
|
||||||
bitcoindConf.protocol,
|
bitcoindConf.protocol,
|
||||||
@ -139,4 +141,5 @@ module.exports = {
|
|||||||
segmentio: process.env.INSIGHT_SEGMENTIO_KEY
|
segmentio: process.env.INSIGHT_SEGMENTIO_KEY
|
||||||
},
|
},
|
||||||
safeConfirmations: safeConfirmations, // PLEASE NOTE THAT *FULL RESYNC* IS NEEDED TO CHANGE safeConfirmations
|
safeConfirmations: safeConfirmations, // PLEASE NOTE THAT *FULL RESYNC* IS NEEDED TO CHANGE safeConfirmations
|
||||||
|
ignoreCache: ignoreCache,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -201,7 +201,6 @@ TransactionDb.prototype._fillOutpoints = function(txInfo, cb) {
|
|||||||
i.value = ret.valueSat / util.COIN;
|
i.value = ret.valueSat / util.COIN;
|
||||||
valueIn += i.valueSat;
|
valueIn += i.valueSat;
|
||||||
|
|
||||||
console.log('[TransactionDb.js.204:ret:]',ret); //TODO
|
|
||||||
if (ret.multipleSpentAttempt || !ret.spentTxId ||
|
if (ret.multipleSpentAttempt || !ret.spentTxId ||
|
||||||
(ret.spentTxId && ret.spentTxId !== txInfo.txid)
|
(ret.spentTxId && ret.spentTxId !== txInfo.txid)
|
||||||
) {
|
) {
|
||||||
@ -209,7 +208,6 @@ console.log('[TransactionDb.js.204:ret:]',ret); //TODO
|
|||||||
ret.multipleSpentAttempts.forEach(function(mul) {
|
ret.multipleSpentAttempts.forEach(function(mul) {
|
||||||
if (mul.spentTxId !== txInfo.txid) {
|
if (mul.spentTxId !== txInfo.txid) {
|
||||||
|
|
||||||
console.log('[TransactionDb.js.210]'); //TODO
|
|
||||||
i.doubleSpentTxID = ret.spentTxId;
|
i.doubleSpentTxID = ret.spentTxId;
|
||||||
i.doubleSpentIndex = ret.spentIndex;
|
i.doubleSpentIndex = ret.spentIndex;
|
||||||
}
|
}
|
||||||
@ -218,7 +216,6 @@ console.log('[TransactionDb.js.210]'); //TODO
|
|||||||
i.dbError = 'Input spent not registered';
|
i.dbError = 'Input spent not registered';
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
console.log('[TransactionDb.js.219]'); //TODO
|
|
||||||
i.doubleSpentTxID = ret.spentTxId;
|
i.doubleSpentTxID = ret.spentTxId;
|
||||||
i.doubleSpentIndex = ret.spentIndex;
|
i.doubleSpentIndex = ret.spentIndex;
|
||||||
}
|
}
|
||||||
@ -409,7 +406,7 @@ TransactionDb.prototype.cacheScriptPubKey = function(txouts,cb) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
TransactionDb.prototype._parseAddrData = function(data) {
|
TransactionDb.prototype._parseAddrData = function(data, ignoreCache) {
|
||||||
var k = data.key.split('-');
|
var k = data.key.split('-');
|
||||||
var v = data.value.split(':');
|
var v = data.value.split(':');
|
||||||
// console.log('[TransactionDb.js.375]',data.key,data.value); //TODO
|
// console.log('[TransactionDb.js.375]',data.key,data.value); //TODO
|
||||||
@ -425,7 +422,7 @@ TransactionDb.prototype._parseAddrData = function(data) {
|
|||||||
// v[1]== isConfirmedCached
|
// v[1]== isConfirmedCached
|
||||||
// v[2]=== '1' -> is SpendCached -> [4]=spendTxId [5]=spentIndex [6]=spendTs
|
// v[2]=== '1' -> is SpendCached -> [4]=spendTxId [5]=spentIndex [6]=spendTs
|
||||||
// v[3]!== '1' -> is ScriptPubkey -> [[3] = scriptPubkey
|
// v[3]!== '1' -> is ScriptPubkey -> [[3] = scriptPubkey
|
||||||
if (v[1]){
|
if (v[1] && !ignoreCache){
|
||||||
item.isConfirmed = 1;
|
item.isConfirmed = 1;
|
||||||
item.isConfirmedCached = 1;
|
item.isConfirmedCached = 1;
|
||||||
// console.log('[TransactionDb.js.356] CACHE HIT CONF:', item.key); //TODO
|
// console.log('[TransactionDb.js.356] CACHE HIT CONF:', item.key); //TODO
|
||||||
@ -448,7 +445,8 @@ TransactionDb.prototype._parseAddrData = function(data) {
|
|||||||
return item;
|
return item;
|
||||||
};
|
};
|
||||||
|
|
||||||
TransactionDb.prototype.fromAddr = function(addr, cb, txLimit) {
|
TransactionDb.prototype.fromAddr = function(addr, opts, cb) {
|
||||||
|
opts = opts || {};
|
||||||
var self = this;
|
var self = this;
|
||||||
var k = ADDR_PREFIX + addr + '-';
|
var k = ADDR_PREFIX + addr + '-';
|
||||||
var ret = [];
|
var ret = [];
|
||||||
@ -456,10 +454,10 @@ TransactionDb.prototype.fromAddr = function(addr, cb, txLimit) {
|
|||||||
db.createReadStream({
|
db.createReadStream({
|
||||||
start: k,
|
start: k,
|
||||||
end: k + '~',
|
end: k + '~',
|
||||||
limit: txLimit>0 ? txLimit: -1, // -1 means not limit
|
limit: opts.txLimit>0 ? opts.txLimit: -1, // -1 means not limit
|
||||||
})
|
})
|
||||||
.on('data', function(data) {
|
.on('data', function(data) {
|
||||||
ret.push(self._parseAddrData(data));
|
ret.push(self._parseAddrData(data, opts.ignoreCache));
|
||||||
})
|
})
|
||||||
.on('error', cb)
|
.on('error', cb)
|
||||||
.on('end', function() {
|
.on('end', function() {
|
||||||
|
|||||||
@ -93,6 +93,17 @@ describe('Address cache ', function() {
|
|||||||
return done();
|
return done();
|
||||||
},{txLimit:0});
|
},{txLimit:0});
|
||||||
});
|
});
|
||||||
|
it('cache case 2 w ignore cache', function(done) {
|
||||||
|
var a = new Address('mt2AzeCorSf7yFckj19HFiXJgh9aNyc4h3', txDb);
|
||||||
|
a.update(function(err) {
|
||||||
|
if (err) done(err);
|
||||||
|
a.balance.should.equal(0, 'balance');
|
||||||
|
a.totalReceived.should.equal(1376000, 'totalReceived');
|
||||||
|
a.txApperances.should.equal(8003, 'txApperances');
|
||||||
|
return done();
|
||||||
|
},{txLimit:0, ignoreCache:1});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user